上篇博文是初用c/c++扩展Python,只是简单的举个例子,有兴趣的可以去上篇博文里看看那个例子的代码,代码如下:

#include<Python.h>staticPyObject*pr_isprime(PyObject*self,PyObject*args){intn,num;if(!PyArg_ParseTuple(args,"i",&num))returnNULL;if(num<1){returnPy_BuildValue("i",0);}n=num-1;while(n>1){if(num%n==0)returnPy_BuildValue("i",0);n--;}returnPy_BuildValue("i",1);}staticPyMethodDefPrMethods[]={{"isPrime",pr_isprime,METH_VARARGS,"checkifaninputnumbeisprimeornot."},{NULL,NULL,0,NULL}};voidinitpr(void){(void)Py_InitModule("pr",PrMethods);}

这两天花时间简单的研究了一下那个代码,其中最关键的是Python.h这头文件,我们可以看看这个头文件的源代码。(用的是Python2.7.12,Ubuntu16.04 LTS,Python.h在/usr/include/python2.7/里)

为了节省篇幅,特意将源代码中注释给删掉,不便之处敬请谅解。

#ifndefPy_PYTHON_H#definePy_PYTHON_H#include"patchlevel.h"#include"pyconfig.h"#include"pymacconfig.h"#ifndefWITH_CYCLE_GC#defineWITH_CYCLE_GC1#endif#include<limits.h>#ifndefUCHAR_MAX#error"Something'sbroken.UCHAR_MAXshouldbedefinedinlimits.h."#endif#ifUCHAR_MAX!=255#error"Python'ssourcecodeassumesC'sunsignedcharisan8-bittype."#endif#ifdefined(__sgi)&&defined(WITH_THREAD)&&!defined(_SGI_MP_SOURCE)#define_SGI_MP_SOURCE#endif#include<stdio.h>#ifndefNULL#error"Python.hrequiresthatstdio.hdefineNULL."#endif#include<string.h>#ifdefHAVE_ERRNO_H#include<errno.h>#endif#include<stdlib.h>#ifdefHAVE_UNISTD_H#include<unistd.h>#endif#ifdefHAVE_STDDEF_H#include<stddef.h>#endif#include<assert.h>#include"pyport.h"#ifndefDL_IMPORT/*declarationsforDLLimport/export*/#defineDL_IMPORT(RTYPE)RTYPE#endif#ifndefDL_EXPORT/*declarationsforDLLimport/export*/#defineDL_EXPORT(RTYPE)RTYPE#endif#ifdefined(Py_DEBUG)&&defined(WITH_PYMALLOC)&&!defined(PYMALLOC_DEBUG)#definePYMALLOC_DEBUG#endif#ifdefined(PYMALLOC_DEBUG)&&!defined(WITH_PYMALLOC)#error"PYMALLOC_DEBUGrequiresWITH_PYMALLOC"#endif#include"pymath.h"#include"pymem.h"#include"object.h"#include"objimpl.h"#include"pydebug.h"#include"unicodeobject.h"#include"intobject.h"#include"boolobject.h"#include"longobject.h"#include"floatobject.h"#ifndefWITHOUT_COMPLEX#include"complexobject.h"#endif#include"rangeobject.h"#include"stringobject.h"#include"memoryobject.h"#include"bufferobject.h"#include"bytesobject.h"#include"bytearrayobject.h"#include"tupleobject.h"#include"listobject.h"#include"dictobject.h"#include"enumobject.h"#include"setobject.h"#include"methodobject.h"#include"moduleobject.h"#include"funcobject.h"#include"classobject.h"#include"fileobject.h"#include"cobject.h"#include"pycapsule.h"#include"traceback.h"#include"sliceobject.h"#include"cellobject.h"#include"iterobject.h"#include"genobject.h"#include"descrobject.h"#include"warnings.h"#include"weakrefobject.h"#include"codecs.h"#include"pyerrors.h"#include"pystate.h"#include"pyarena.h"#include"modsupport.h"#include"pythonrun.h"#include"ceval.h"#include"sysmodule.h"#include"intrcheck.h"#include"import.h"#include"abstract.h"#include"compile.h"#include"eval.h"#include"pyctype.h"#include"pystrtod.h"#include"pystrcmp.h"#include"dtoa.h"PyAPI_FUNC(PyObject*)_Py_Mangle(PyObject*p,PyObject*name);#definePyArg_GetInt(v,a)PyArg_Parse((v),"i",(a))#definePyArg_NoArgs(v)PyArg_Parse(v,"")#definePy_CHARMASK(c)((unsignedchar)((c)&0xff))#include"pyfpe.h"#definePy_single_input256#definePy_file_input257#definePy_eval_input258#ifdefHAVE_PTH#include<pth.h>#endif#definePyDoc_VAR(name)staticcharname[]#definePyDoc_STRVAR(name,str)PyDoc_VAR(name)=PyDoc_STR(str)#ifdefWITH_DOC_STRINGS#definePyDoc_STR(str)str#else#definePyDoc_STR(str)""#endif#endif/*!Py_PYTHON_H*/

代码没几句,就是一堆头文件,而且在Python.h文件里没有找到 PyArg_ParseTuple()、Py_BuildValue()、PyMethodDef、PrMethods、METH_VARARGS、Py_InitModule这些变量或者函数。说实话,我第一看也纳闷呀,怎么Python.h文件里没有这些变量或者函数呢?所以很快就想到一定是在包含的头文件里的某些文件里,这么多,怎么找呀?我是写脚本程序找的,脚本程序很简单,在此就不贴代码了,几秒钟就找到了这些函数或者变量是在哪个文件里定义的。下面来一一介绍这几个变量或者函数吧,有不正确的地方,欢迎批评指正。

(1)PyArg_ParseTuple()

该函数定义在/usr/include/python2.7/modsupport.h里。这个文件里有一段文字解释——”Module support interface“,也就是模块支持接口,这个文件里应该就是定义了对外扩展的接口。这个函数的原型是:

PyAPI_FUNC(int)PyArg_ParseTuple(PyObject*,constchar*,...)

该函数的功能是将Python对象C/C++类型数据,如果转换失败,返回0

第一个参数:包含从Python传递到C函数的参数列表的元组对象

第二个参数:是格式参数,必须是字符串,已经预定义好了的,零个或多个“格式单位”组成。一个格式单元描述一个Python对象。比如例子中的‘i'表示将Python整数对象转换为纯C语言的 int类型。

其余参数:其余参数必须是其类型由格式字符串确定的变量的地址,可以是多个地址。上面例子用的就 是num的地址&num表示的就是num的地址,&是取值运算符

一些常见的格式参数:

"s":将Python字符串或Unicode对象转换为C里面字符串的指针,即Python中string o或者Unicode 对象转换为C语言里 char *

“s#”:“s”上的这个变体存储到两个C变量中,第一个是指向字符串的指针,第二个是它的长度。在这种情况下,Python字符串可能包含嵌入的空字节。如果可以进行这种转换,Unicode对象将传回指向对象的默认编码字符串版本的指针。所有其他读缓冲区兼容对象传回对原始内部数据表示的引用。即(字符串,Unicode或任何读取缓冲区兼容对象)→[char *,int]。

“z”:像“s”,但Python对象也可以是None,在这种情况下,C指针设置为NULL。即string或None)→[char *]

“z#”:(字符串或无或任何读缓冲区兼容对象)→[char *,int]。

“u”:将Python Unicode对象转换为C指针,指向16位Unicode(UTF-16)数据的空终止缓冲区。即(Unicode对象)→[Py_UNICODE *]。

“u#”:这个变量“u”存储到两个C变量中,第一个是指向Unicode数据缓冲区的指针,第二个是它的长度。(Unicode对象)→[Py_UNICODE *,int]。

“es”:“s”上的此变体用于将Unicode和可转换为Unicode的对象编码为字符缓冲区。它只适用于没有嵌入NULL字节的编码数据。变量读取一个变量并存储到两个C变量中,第一个是指向编码名称字符串(编码)的指针,第二个是指向字符缓冲区的指针的指针,即(字符串,Unicode对象或字符缓冲区兼容对象)→[const char * encoding,char ** buffer]。

“es#”:类似”es",只是第三个指向整数的指针(* buffer_length,缓冲区长度)。编码名称必须映射到注册的编×××。如果设置为NULL,则使用默认编码。即(字符串,Unicode对象或字符缓冲区兼容对象)→[const char * encoding,char ** buffer,int * buffer_length]。

“h”:将Python整数转换为C short int,即(integer)→[short int]

“i”:将Python整数转换为纯C int。即(integer)→[int]

“l”:将Python整数转换为C long int,即(integer)→[long int]

“c”:将一个Python字符(表示为长度为1的字符串)转换为C char,即(长度为1的字符串)→[char]

“f”:将Python浮点数转换为C浮点,即(float)→[float]

“d”:将Python浮点数转换为C double,即(float)→[double]

“D”:将Python复杂数字转换为C Py_complex结构,即(复合物)→[Py_complex]

“O”:将Python对象(无任何转换)存储在C对象指针中。 C程序因此接收被传递的实际对象。对象的引用计数不增加。存储的指针不为NULL。(object)→[PyObject *]

“O!":将Python对象存储在C对象指针。这类似于“O”,但有两个C参数:第一个是Python类型对象的地址,第二个是存储对象指针的C变量(类型PyObject *)的地址。如果Python对象没有必需的类型,则会引发TypeError。(object)→[typeobject,PyObject *]

“O&”:通过转换器函数将Python对象转换为C变量。这需要两个参数:第一个是一个函数,第二个是C变量(任意类型)的地址,(object)→[converter,anything]

“S”:像“O”,但要求Python对象是一个字符串对象。如果对象不是字符串对象,则引发TypeError。 C变量也可以声明为PyObject *。(string)→[PyStringObject *]

“u”:像“O”,但要求Python对象是一个Unicode对象。如果对象不是Unicode对象,则引发TypeError。 C变量也可以声明为PyObject *。(Unicode字符串)→[PyUnicodeObject *]

“t#”:类似“s#”,但接受任何实现只读缓冲区接口的对象。 char *变量被设置为指向缓冲区的第一个字节,int被设置为缓冲区的长度。只接受单段缓冲对象;对所有其他类型引发TypeError。(只读字符缓冲区)→[char *,int]

“w”:类似于“s”,但接受实现读写缓冲器接口的任何对象。调用者必须通过其他方式确定缓冲区的长度,或者使用“w#”。只接受单段缓冲对象;对所有其他类型引发TypeError。(读写字符缓冲区)→[char *]

“w#”:类似“s#”,但接受任何实现读写缓冲区接口的对象。 char *变量被设置为指向缓冲区的第一个字节,int被设置为缓冲区的长度。只接受单段缓冲对象;对所有其他类型引发TypeError。(读写字符缓冲区)→[char *,int]

“items”:对象必须是Python序列,其长度是项目中的格式单位数。 C参数必须对应于各个格式单元initem。 可以嵌套序列的格式单元。(tuple)→[matching-items]


如果对Python源码稍微有点了解的话,PyObject 、PyStringObject 、PyUnicodeObject等都是Python源码里用C语言为Python定义的类型,有兴趣的可以看看《Python源码解析》这本书,里面都有介绍。


另外还有一些其他字符在格式字符串中有意义,

“|”:表示Python参数列表中的其余参数是可选的。 对应于可选参数的C变量应该被初始化为它们的默认值 - 当没有指定可选参数时,PyArg_ParseTuple()不触及相应的C变量的内容。

“:”:格式单元列表在这里结束; 冒号之后的字符串用作错误消息中的函数(“PyArg_ParseTuple()”引发的异常的“关联值”)。

“;”:格式单元列表在这里结束; 冒号之后的字符串用作错误消息,而不是默认错误消息。 显然,“:”和“;” 互相排斥。


(2)Py_BuildValue()

该函数也是定义在/usr/include/python2.7/modsupport.h里,原型如下:

PyAPI_FUNC(PyObject*)Py_BuildValue(constchar*,...)


它的功能与PyArg_ParseTuple()正好相反,它是将C类型的数据结构转换成Python对象。

它第一个参数是格式参数,同PyArg_ParseTuple()格式参数一样,其余参数就是一些C类型的数据咯。

看几个例子吧,对它的理解会有帮助。

Py_BuildValue("") None

Py_BuildValue("i", 123) 123

Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)

Py_BuildValue("s", "hello") 'hello'

Py_BuildValue("ss", "hello", "world") ('hello', 'world')

Py_BuildValue("s#", "hello", 4) 'hell' #这个还有一个长度限制

Py_BuildValue("()") ()

Py_BuildValue("(i)", 123) (123,)

Py_BuildValue("(ii)", 123, 456) (123, 456)

Py_BuildValue("(i,i)", 123, 456) (123, 456)

Py_BuildValue("[i,i]", 123, 456) [123, 456]

Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456) {'abc': 123, 'def': 456}

Py_BuildValue("((ii)(ii)) (ii)", 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))


(3)Py_InitModule()

该函数也是定义在/usr/include/python2.7/modsupport.h里,返回一个指针指向刚创建的模块对象,看名字也知道是初始化新建模块的。函数原型:

#definePy_InitModule(name,methods)\Py_InitModule4(name,methods,(char*)NULL,(PyObject*)NULL,\PYTHON_API_VERSION)

是宏定义,接受两个参数,第一个参数为字符串,表示模块的名称;第二个参数是一个PyMethodDef的结构体数组,表示该模块都具有哪些方法。

PyMethodDef结构体有四个字段。

* 第一个是一个字符串,表示在Python中对应的方法的名称;

* 第二个是对应的C代码的函数;

* 第三个是一个标致位,表示该Python方法是否需要参数,METH_NOARGS表示不需要参数,METH_VARARGS表示需要参数,这个参数在/usr/include/python2.7/methodobject.h有定义;

* 第四个是一个字符串,它是该方法的__doc__属性,这个不是必须的,可以为NULL。

其源代码如下:

structPyMethodDef{constchar*ml_name;/*Thenameofthebuilt-infunction/method*/PyCFunctionml_meth;/*TheCfunctionthatimplementsit*/intml_flags;/*CombinationofMETH_xxxflags,whichmostlydescribetheargsexpectedbytheCfunc*/constchar*ml_doc;/*The__doc__attribute,orNULL*/};typedefstructPyMethodDefPyMethodDef;

是在/usr/include/python2.7/methodobject.h中定义的。

PyMethodDef结构体数组最后以 {NULL, NULL, 0, NULL}结尾。(感觉好像不是必须的,但是通常都这么做那我们也这么做吧)不正之处,欢迎批评指正!