1.使用C或C ++扩展Python – 扩展和嵌入Python解释器(Python教程)(参考资料)

1.使用C或C ++扩展Python

向Python中添加新的内置模块非常容易,如果你知道C中的toprogram如何。这样extension modules可以做两件事就可以’t bedone直接在Python中:它们可以实现新的内置对象类型,它们可以调用C库函数和系统调用.

为了支持扩展,Python API(应用程序编程接口)定义了一组函数,宏和变量,提供对Python运行时系统的大多数方面的访问。Python API包含在一个Csource文件中,包含标题"Python.h".

扩展模块的编译取决于它的预期用途以及你的系统设置;细节在后面的章节中给出.

注意

C扩展接口特定于CPython,扩展模块不适用于其他Python实现。在许多情况下,可以避免编写C扩展并保留其他实现的可移植性。例如,如果您的用例是调用C库函数或系统调用,您应该考虑使用ctypes模块或cffi库而不是编写自定义C代码。这些模块允许您编写Python代码以与C代码接口,并且在Python的实现之间比在编写和编译Cextension模块时更具便携性.

 

1.1。一个简单的例子

让我们创建一个名为spam的扩展模块(MontyPython粉丝最喜欢的食物……),假设我们要为Clibrary函数创建一个Python接口system()[1]。此函数将以null结尾的字符串作为参数,并返回一个整数。我们希望这个函数可以从Python调用,如下所示:

>>> import spam  >>> status = spam.system("ls -l")

首先创建一个文件spammodule.c。(从历史上看,如果一个模块被称为spam,则包含其实现的C文件被称为spammodule.c;如果模块名称很长,如spammify,则模块名称可以只是spammify.c.)

我们文件的第一行可以是:

#include <Python.h>

它引入了Python API(您可以添加描述模块目的的注释和版权声明如果您愿意).

注意

由于Python可能会定义一些影响某些系统上标准头的预处理器定义,所以你must包含Python.h之前包含任何标准头.

Python.h定义的所有用户可见符号的前缀为PyPY,标准头文件中定义的除外。为方便起见,并且由于它们被Python解释器广泛使用,"Python.h"包含一些标准头文件:<stdio.h>, <string.h>,<errno.h><stdlib.h>。如果你的系统上不存在后一个头文件,它会直接声明malloc(), free()realloc()的功能

接下来我们添加到模块文件中的是C函数,当Python表达式spam.system(string)被评估时将被调用(我们将很快看到它最终如何被调用):

static PyObject *  spam_system(PyObject *self, PyObject *args)  {      const char *command;      int sts;        if (!PyArg_ParseTuple(args, "s", &command))          return NULL;      sts = system(command);      return PyLong_FromLong(sts);  }

有一个从Python中的参数列表(例如,单个表达式"ls -l")直接转换为传递给Cfunction的参数。C函数总是有两个参数,通常命名为selfargs.

self参数指向模块级函数的模块对象;对于一个方法,它指向object instance.

args参数将是一个指向包含参数的Python元组对象的指针。元组的每个项对应于call’sargument列表中的参数。参数是Python对象 – 为了在我们的C函数中对它们做任何事情,我们必须将它们转换为C值。功能PyArg_ParseTuple()在Python API中检查参数类型并将它们转换为C值。它使用模板字符串来确定参数的必需类型以及将转换后的值转换为其中的C变量的类型。稍后详细说明.

PyArg_ParseTuple()如果所有参数都具有righttype且其组件已存储在地址被占用的变量中,则返回true(非零)。如果传递了无效的参数列表,则返回false(零)。在thelatter的情况下,它也提出了一个适当的例外,因此调用函数可以立即返回NULL(如我们在例子中所见).

 

1.2。Intermezzo:错误和异常

整个Python解释器的一个重要约定如下:当一个函数失败时,它应该设置一个异常条件并返回一个错误值(通常是NULL指针)。异常存储在解释器中的静态全局变量中;如果这个变量是NULL没有例外。第二个全局变量存储异常的“关联值”(第二个参数为raise)。第三个变量包含stacktraceback,以防错误源自Python代码。这三个变量是sys.exc_info()的Python结果的C等价物(参见Python库参考中的模块sys上的章节)。重要的是了解它们以了解错误是如何传递的.

Python API定义了许多函数来设置各种类型的异常.

最常见的是PyErr_SetString()。它的参数是一个异常对象和一个C字符串。异常对象通常是一个预定义的对象,如PyExc_ZeroDivisionError。C字符串表示errorand的原因被转换为Python字符串对象并存储为异常的“关联值”.

另一个有用的函数是PyErr_SetFromErrno(),它只接受一个异常参数并通过检查全局变量构造相关值errno。最通用的函数是PyErr_SetObject(),它接受两个对象参数,异常及其相关值。你不需要Py_INCREF()传递给任何这些函数的对象.

您可以非破坏性地测试是否已使用PyErr_Occurred()设置了异常。这将返回当前的异常对象,如果没有发生异常,则返回NULL。你通常不需要打电话PyErr_Occurred()查看函数调用中是否发生错误,因为你应该能够从返回值中判断.

当一个函数f调用另一个函数时g检测到latterfails f本身应该返回一个错误值(通常是NULL-1)。它应该not调用其中一个PyErr_*()功能 – 一个已经被g. f然后,调用者也应该返回错误指示its来电者without打电话PyErr_*()等等 – 错误的最详细原因已经由首先检测到它的功能报告。一旦错误到达Python解释器的mainloop,这将中止当前正在执行的Python代码并尝试查找由Python程序员指定的一个异常处理程序.

(有些情况下模块可以通过调用实际提供更详细的错误消息另一个PyErr_*()功能,在这种情况下,它是这样做的。但是,作为一般规则,这不是必要的,并且可能导致关于错误原因的信息丢失:大多数操作可能会失败多种原因。)

要忽略失败的函数调用设置的异常,必须通过调用PyErr_Clear()显式清除异常条件。C代码应该调用PyErr_Clear()的唯一时间是它是否不想将错误传递给解释器但是想要自己完全处理它(可能是通过尝试别的东西,或者假装什么都没有出错).

每次失败malloc()呼叫必须变成异常 – malloc()(要么 realloc())必须打电话PyErr_NoMemory()并返回故障指示器本身。所有对象创建功能(例如PyLong_FromLong())已经是dothis,所以这个注释只与那些直接调用malloc()的人有关.

另请注意,除了PyArg_ParseTuple() andfriends的重要例外,返回整数状态的函数通常会返回正值或零以表示成功和-1失败,比如Unix系统调用.

最后,当你返回错误时,小心清理垃圾(通过调用Py_XDECREF()Py_DECREF()调用你已创建的对象)指示符!

选择哪个例外完全属于你。有preclaredC对象对应于所有内置的Python异常,例如PyExc_ZeroDivisionError,你可以直接使用。当然,你应该明智地选择异常 – 不要使用PyExc_TypeError来表示文件无法打开(应该是PyExc_IOError如果参数列表有问题,PyArg_ParseTuple()函数通常会引发PyExc_TypeError。如果你的参数的值必须在特定的范围内或者必须满足其他条件,那么PyExc_ValueError是合适的.

你也可以定义一个对你的模块来说唯一的新异常。为此,通常在文件的开头声明一个静态对象变量:

static PyObject *SpamError;

并在模块的初始化函数(PyInit_spam())中使用异常对象初始化它(暂时不进行错误检查)):

PyMODINIT_FUNC  PyInit_spam(void)  {      PyObject *m;        m = PyModule_Create(&spammodule);      if (m == NULL)          return NULL;        SpamError = PyErr_NewException("spam.error", NULL, NULL);      Py_INCREF(SpamError);      PyModule_AddObject(m, "error", SpamError);      return m;  }

请注意,异常对象的Python名称是spam.errorPyErr_NewException()函数可以创建一个带有基类Exception的类(除非传入另一个类而不是NULL),如所述内置例外.

还要注意SpamError变量保留对新创建的异常类的引用;这是故意的!由于可以通过外部代码从模块中删除异常,因此需要对类的自有引用以确保它不会被丢弃,从而导致SpamError成为悬空指针。如果它成为一个悬空指针,C代码引起异常可能导致核心转储或其他意外的副作用.

我们讨论在本示例后面使用PyMODINIT_FUNC作为函数返回类型.

使用acall到spam.error可以在扩展模块中引发PyErr_SetString()异常,如下所示:

static PyObject *  spam_system(PyObject *self, PyObject *args)  {      const char *command;      int sts;        if (!PyArg_ParseTuple(args, "s", &command))          return NULL;      sts = system(command);      if (sts < 0) {          PyErr_SetString(SpamError, "System command failed");          return NULL;      }      return PyLong_FromLong(sts);  }

 

1.3。回到示例

回到我们的示例函数,您现在应该能够理解这句话:

if (!PyArg_ParseTuple(args, "s", &command))      return NULL;

它返回NULL(函数返回对象指针的错误指示符)如果在参数列表中检测到错误,则依赖于PyArg_ParseTuple()设置的异常。否则参数的字符串值已被复制到局部变量command。这是一个指针赋值,你不应该修改它指向的字符串(所以在标准C中,变量command应该正确地声明为const char*command)。

下一个语句是调用Unix函数system(),传递我们刚刚从PyArg_ParseTuple()

sts = system(command);

我们的 spam.system()函数必须返回sts作为Python对象。这是使用函数PyLong_FromLong().

return PyLong_FromLong(sts);

完成的。在这种情况下,它将返回一个整数对象。(是的,甚至整数都是Python中堆上的对象!)

如果你有一个C函数没有返回有用的参数(一个函数返回void),相应的Python函数必须返回None。你需要这个成语(由实现)Py_RETURN_NONEmacro):

Py_INCREF(Py_None);  return Py_None;

Py_None是特殊Python对象的//名称None。它是一个非常有用的Python对象而不是NULL指针,这意味着大多数时候都有“错误”,正如我们所看到的那样.

 

1.4。模块的方法表和初始化函数

我答应如何展示spam_system()从Python程序中调用。首先,我们需要在“方法表”中列出其名称和地址:

static PyMethodDef SpamMethods[] = {      ...      {"system",  spam_system, METH_VARARGS,       "Execute a shell command."},      ...      {NULL, NULL, 0, NULL}        /* Sentinel */  };

注意第三个条目(METH_VARARGS)。这是一个告诉解释调用约定用于C函数的标志。它通常应该总是METH_VARARGSMETH_VARARGS | METH_KEYWORDS;值0表示使用PyArg_ParseTuple()的过时变体.

当只使用METH_VARARGS时,该函数应该期望传递Python级参数作为通过PyArg_ParseTuple()解析可接受的元组;有关此函数的更多信息如下所示.

如果应将keywordarguments传递给函数,则可以在第三个字段中设置METH_KEYWORDS位。在这种情况下,C函数应该接受第三个PyObject *参数,该参数将是关键字的字典。使用PyArg_ParseTupleAndKeywords()来解析这些函数的参数.

方法表必须在模块定义结构中引用:

static struct PyModuleDef spammodule = {      PyModuleDef_HEAD_INIT,      "spam",   /* name of module */      spam_doc, /* module documentation, may be NULL */      -1,       /* size of per-interpreter state of the module,                   or -1 if the module keeps state in global variables. */      SpamMethods  };

反过来,这个结构必须传递给模块初始化函数中的解释器。初始化函数必须命名为PyInit_name(),其中name是模块的名称,并且应该是模块文件中定义的非static项:

PyMODINIT_FUNCPyInit_spam(void){      return PyModule_Create(&spammodule);  }

注意PyMODINIT_FUNC声明函数为PyObject *返回类型,声明平台所需的任何特殊链接声明,并且对于C ++声明函数为extern "C".

当Python程序导入模块spam第一次调用PyInit_spam()。(有关嵌入Python的注释,请参见下文。)它调用PyModule_Create(),它返回一个模块对象,并根据thetable(PyMethodDef结构数组)将内置函数对象插入到新创建的模块中)在模块定义中找到.PyModule_Create()返回一个指向它创建的模块对象的指针。它可能会因致命错误而中止,或者返回NULL如果模块无法初始化。init函数必须将模块对象返回给它的调用者,然后将其插入到sys.modules.

嵌入Python时,PyInit_spam()函数不是自动调用的,除非在PyImport_Inittab表中有一个条目。要将模块添加到初始化表中,使用PyImport_AppendInittab(),可选地后面导入模块:

int  main(int argc, char *argv[])  {      wchar_t *program = Py_DecodeLocale(argv[0], NULL);      if (program == NULL) {          fprintf(stderr, "Fatal error: cannot decode argv[0]n");          exit(1);      }        /* Add a built-in module, before Py_Initialize */      PyImport_AppendInittab("spam", PyInit_spam);        /* Pass argv[0] to the Python interpreter */      Py_SetProgramName(program);        /* Initialize the Python interpreter.  Required. */      Py_Initialize();        /* Optionally import the module; alternatively,         import can be deferred until the embedded script         imports it. */      PyImport_ImportModule("spam");        ...        PyMem_RawFree(program);      return 0;  }

注意

sys.modules中删除条目或在一个进程中导入已编译的模块和多个解释器(或者在fork()之后没有介入exec())可以为某些扩展模块创建问题。扩展模块作者在初始化内部数据结构时应该小心.

一个更实质的示例模块包含在Python源代码中Modules/xxmodule.c。这个文件可以作为模板使用,也可以简单地作为例子.

注意

不像我们的spam示例,xxmodule使用multi-phase initialization(Python 3.5中的新增功能),其中从PyInit_spam并且模块的创建留给了导入机器。有关多相初始化的详细信息,请参阅 PEP 489 .

 

1.5。编译和链接

在使用新扩展之前还有两件事要做:编译并将其与Python系统链接。如果使用动态加载,则详细信息可能取决于系统使用的动态加载方式;请参阅构建扩展模块的章节(章节构建C和C ++扩展)以及仅适用于在Windows上构建的其他信息(章节在Windows上构建C和C ++扩展))有关this的更多信息.

如果你不能使用动态加载,或者如果你想让你的模块成为Python解释器的永久部分,你将不得不改变配置setup并重建解释器。幸运的是,在Unix上这很简单:只需将你的文件(例如spammodule.c)放在一个解压缩的源代码分发的Modules/目录中,在文件中添加一行Modules/Setup.local返回PyModuleDef结构,描述你的文件:

spam spammodule.o

并通过运行 make重建解释器在topleve目录中。你也可以跑使在里面 Modules/子目录,但是你必须先重建Makefile在那里跑’使Makefile文件”。(每次更改Setup文件时都需要这样做。)

如果您的模块需要链接其他库,这些库也可以在配置文件的行中列出,例如:

spam spammodule.o -lX11

 

1.6。从C 中调用Python函数

到目前为止,我们专注于使C函数可以从Python调用。反向也很有用:从C调用Python函数。这尤其适用于支持所谓“回调”函数的库。如果Cinterface使用回调,则等效的Python通常需要为Python程序员提供回调机制;实现将需要从C回调中调用Python回调函数。其他用途也是可以想象的.

幸运的是,Python解释器很容易递归调用,并且有一个标准的接口来调用Python函数。(我不会详细说明如何使用特定字符串作为输入来调用thePython解析器 – 如果您感兴趣,请参考-c中的Modules/main.c命令行选项的实现Python源代码。)

调用Python函数很容易。首先,Python程序必须以某种方式传递Python函数对象。您应该提供一个函数(或其他一些接口)来执行此操作。调用此函数时,在globalvariable中保存指向thePython函数对象的指针(小心Py_INCREF()它!),或者在你认为合适的地方。例如,以下函数可能是模块定义的一部分:

static PyObject *my_callback = NULL;    static PyObject *  my_set_callback(PyObject *dummy, PyObject *args)  {      PyObject *result = NULL;      PyObject *temp;        if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {          if (!PyCallable_Check(temp)) {              PyErr_SetString(PyExc_TypeError, "parameter must be callable");              return NULL;          }          Py_XINCREF(temp);         /* Add a reference to new callback */          Py_XDECREF(my_callback);  /* Dispose of previous callback */          my_callback = temp;       /* Remember new callback */          /* Boilerplate to return "None" */          Py_INCREF(Py_None);          result = Py_None;      }      return result;  }

必须使用METH_VARARGS标志向解释器注册此函数;这在模块的方法表和初始化函数中描述。PyArg_ParseTuple()函数及其参数记录在在扩展函数中提取参数.

Py_XINCREF()Py_XDECREF()递增/递减对象的参考计数,并且在NULL指针存在时是安全的(但是注意temp在这种情况下不会是NULL)。关于themin部分的更多信息参考计数.

以后,当需要调用该函数时,调用C函数PyObject_CallObject()。这个函数有两个参数,都是指向任意Python对象的指针:Python函数和参数列表。参数列表必须始终是元组对象,其长度是参数的数量。要调用没有参数的Python函数,传入NULL,oran空元组;用一个参数调用它,传递一个单例元组.Py_BuildValue()当它的格式字符串由括号中的零或更多格式代码组成时返回一个元组。例如:

int arg;  PyObject *arglist;  PyObject *result;  ...  arg = 123;  ...  /* Time to call the callback */  arglist = Py_BuildValue("(i)", arg);  result = PyObject_CallObject(my_callback, arglist);  Py_DECREF(arglist);

PyObject_CallObject()返回一个Python对象指针:这是Python函数的返回值。PyObject_CallObject()关于其论点,它是“引用计数中立的”。在这个例子中,创建了一个newtuple作为参数列表,在Py_DECREF()call.PyObject_CallObject()之后立即

的返回值PyObject_CallObject()是“新的”:它是一个全新的对象,或者它是一个已经增加了引用计数的现有对象。所以,除非你想把它保存在一个全局变量中,你应该以某种方式Py_DECREF()结果,甚至(特别是!),如果你对它的价值不感兴趣.

然而,在你这样做之前,重要的是检查返回值是不是NULL。如果是,则通过引发异常来终止Python函数。如果调用了的C代码PyObject_CallObject()从Python调用它,它现在应该向它的Python调用者返回一个错误指示,因此解释器可以打印堆栈跟踪,或者调用Python代码可以处理异常。如果这不可能或不可取,则应该通过调用来清除异常PyErr_Clear()。例如:

if (result == NULL)      return NULL; /* Pass error back */  ...use result...  Py_DECREF(result);

根据所需的Python回调函数接口,您还可以为PyObject_CallObject()。在某些情况下,参数列表也由Python程序提供,通过指定回调函数的相同接口。然后可以以与功能对象相同的方式保存和使用它。在其他情况下,您可能会将新元组作为参数列表进行传递。最简单的方法是调用Py_BuildValue()。例如,如果要传递integralevent代码,可以使用以下代码:

PyObject *arglist;  ...  arglist = Py_BuildValue("(l)", eventcode);  result = PyObject_CallObject(my_callback, arglist);  Py_DECREF(arglist);  if (result == NULL)      return NULL; /* Pass error back */  /* Here maybe use the result */  Py_DECREF(result);

注意Py_DECREF(arglist)通话结束后,错误检查前!另请注意,严格来说这段代码不完整:Py_BuildValue()可能内存不足,应该检查一下

您也可以使用PyObject_Call()调用带有关键字参数的函数,该函数支持参数和关键字参数。如上例所示,我们使用Py_BuildValue()来构造字典.

PyObject *dict;  ...  dict = Py_BuildValue("{s:i}", "name", val);  result = PyObject_Call(my_callback, NULL, dict);  Py_DECREF(dict);  if (result == NULL)      return NULL; /* Pass error back */  /* Here maybe use the result */  Py_DECREF(result);

 

1.7。在扩展函数中提取参数

PyArg_ParseTuple()函数声明如下:

int PyArg_ParseTuple(PyObject *arg, const char *format, ...);

arg参数必须是一个元组对象,包含从Python传递到的参数列表一个C函数。format参数必须是格式字符串,其语法在中解释在Python / C API ReferenceManual中解析参数和构建值。其余参数必须是变量的地址,其类型由格式字符串确定.

注意虽然PyArg_ParseTuple()检查Python参数是否具有所需类型,但它无法检查C的地址的有效性变量通过调用:如果你在那里犯错,你的代码可能会崩溃,至少会覆盖内存中的随机位。所以要小心!

请注意,提供给调用者的任何Python对象引用都是borrowed引用;不要减少他们的引用数!

一些例子调用:

#define PY_SSIZE_T_CLEAN  /* Make "s#" use Py_ssize_t rather than int. */  #include <Python.h>

int ok;  int i, j;  long k, l;  const char *s;  Py_ssize_t size;    ok = PyArg_ParseTuple(args, ""); /* No arguments */      /* Python call: f() */

ok = PyArg_ParseTuple(args, "s", &s); /* A string */      /* Possible Python call: f("whoops!") */

ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */      /* Possible Python call: f(1, 2, "three") */

ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);      /* A pair of ints and a string, whose size is also returned */      /* Possible Python call: f((1, 2), "three") */

{      const char *file;      const char *mode = "r";      int bufsize = 0;      ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);      /* A string, and optionally another string and an integer */      /* Possible Python calls:         f('spam')         f('spam', 'w')         f('spam', 'wb', 100000) */  }

{      int left, top, right, bottom, h, v;      ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",               &left, &top, &right, &bottom, &h, &v);      /* A rectangle and a point */      /* Possible Python call:         f(((0, 0), (400, 300)), (10, 10)) */  }

{      Py_complex c;      ok = PyArg_ParseTuple(args, "D:myfunction", &c);      /* a complex, also providing a function name for errors */      /* Possible Python call: myfunction(1+2j) */  }

 

1.8。扩展功能的关键字参数

PyArg_ParseTupleAndKeywords()函数声明如下:

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,                                  const char *format, char *kwlist[], ...);

argformat参数与PyArg_ParseTuple()函数的参数相同。kwdictparameter是从Python运行时作为第三个参数接收的关键字的字典。kwlist参数是NULL– 终止的字符串列表,用于标识参数;名称与来自左侧的format的类型信息匹配。成功的时候,PyArg_ParseTupleAndKeywords()返回true,否则返回false并引发相应的异常.

注意

使用关键字参数时无法解析嵌套元组!kwlist中不存在的关键字参数将导致TypeError被抬起

这是一个使用关键字的示例模块,基于GeoffPhilbrick的例子(philbrick @ hks . com):

#include "Python.h"    static PyObject *  keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)  {      int voltage;      const char *state = "a stiff";      const char *action = "voom";      const char *type = "Norwegian Blue";        static char *kwlist[] = {"voltage", "state", "action", "type", NULL};        if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,                                       &voltage, &state, &action, &type))          return NULL;        printf("-- This parrot wouldn't %s if you put %i Volts through it.n",             action, voltage);      printf("-- Lovely plumage, the %s -- It's %s!n", type, state);        Py_RETURN_NONE;  }    static PyMethodDef keywdarg_methods[] = {      /* The cast of the function is necessary since PyCFunction values       * only take two PyObject* parameters, and keywdarg_parrot() takes       * three.       */      {"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,       "Print a lovely skit to standard output."},      {NULL, NULL, 0, NULL}   /* sentinel */  };    static struct PyModuleDef keywdargmodule = {      PyModuleDef_HEAD_INIT,      "keywdarg",      NULL,      -1,      keywdarg_methods  };    PyMODINIT_FUNC  PyInit_keywdarg(void)  {      return PyModule_Create(&keywdargmodule);  }

 

1.9。建立任意值

这个功能是PyArg_ParseTuple()。它被声明如下:

PyObject *Py_BuildValue(const char *format, ...);

它识别一组类似于PyArg_ParseTuple(),但参数(输入到函数,而不是输出)必须不是指针,只是值。它返回一个新的Python对象,适合从Python调用的C函数返回

PyArg_ParseTuple()的差异:而后者要求它的第一个参数是一个元组(因为Python参数列表是总是代表内部的元组),Py_BuildValue()并不总是建立一个元组。只有当格式字符串包含两个或更多格式单元时,它才会构建元组。如果格式字符串为空,则返回None;如果它只包含一个格式单位,则返回该格式单位描述的任何对象。Toforce它返回一个大小为0或1的元组,将格式字符串括起来.

Examples(左边是调用,右边是生成的Python值):

Py_BuildValue("")                        None  Py_BuildValue("i", 123)                  123  Py_BuildValue("iii", 123, 456, 789)      (123, 456, 789)  Py_BuildValue("s", "hello")              'hello'  Py_BuildValue("y", "hello")              b'hello'  Py_BuildValue("ss", "hello", "world")    ('hello', 'world')  Py_BuildValue("s#", "hello", 4)          'hell'  Py_BuildValue("y#", "hello", 4)          b'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))

 

1.10。参考计数

在C或C ++等语言中,程序员负责堆上内存的动态分配和释放。在C中,这是使用函数malloc()free()识别的格式单位。在C ++中,运算符newdelete使用的含义基本相同,我们将以下讨论限制在C的情况下

malloc()最后应该通过一次调用free()来回到可用内存池中。在正确的时间调用free()很重要。如果一个区块的地址被遗忘但是free()没有调用它,它占用的内存在程序终止之前不能重用。这称为memoryleak。另一方面,如果程序调用free()对于一个块然后继续使用该块,它通过另一个malloc()调用重新使用该块会产生冲突。这就是所谓的 using freed memory它与引用未初始化的数据有相同的不良后果 – coredumps,错误的结果,神秘的崩溃.

内存泄漏的常见原因是代码中的异常路径。例如,函数可以分配一块内存,进行一些计算,然后再次释放块。现在,对函数要求的更改可能会在检测错误条件的计算中添加一个测试,并且可以从函数中提前返回。在进行此过早退出时,很容易忘记释放已分配的内存块,尤其是稍后将其添加到代码中时。这样的泄漏一旦被引入,通常很长时间都不会被检测到:错误退出仅在所有调用的一小部分中进行,并且大多数现代机器具有大量虚拟内存,因此泄漏仅在使用中的运行过程中变得明显。经常泄漏功能。因此,通过使用最小化此类错误的编码约定来防止泄漏是非常重要的.

因为Python大量使用malloc()free(),它需要战略性以避免内存泄漏以及释放内存的使用。选择的方法称为reference counting。原理很简单:everyobject包含一个计数器,当对象的引用存储在某处时会递增,当删除对它的引用时递减。当计数器达到零时,对象的最后一个引用被删除,对象被释放

另一种策略称为automatic garbage collection.(有时候,引用计数也被称为垃圾收集策略,因此我使用“自动”来区分这两者。)自动垃圾收集的一大优点是用户不需要显式调用free()。(另一个声称的优点是速度或内存使用的改进 – 但这并不是一件难事。)缺点是forC,没有真正的便携式自动垃圾收集器,而引用计数可以实现便携式实现(只要函数malloc()free()可用 – C标准保证)。也许有一天,一个足够便携的自动垃圾收集器将可用于C.Until然后,我们将不得不忍受引用计数.

虽然Python使用传统的引用计数实现,它还提供了一个工作的循环检测器检测参考周期。这使得应用程序不必担心创建直接或间接循环引用;这些是仅使用引用计数实现的垃圾收集的弱点。引用循环由包含(可能是间接的)对自身的引用的对象组成,因此循环中的每个对象都具有非零的引用计数。典型的引用计数实现无法回收属于引用周期中任何对象的内存,或者从周期中的对象引用,即使没有对周期本身的进一步引用.

循环检测器能够检测垃圾循环并可以回收它们.gc模块暴露了一种运行检测器的方法(collect()功能),以及配置接口和禁用它的能力检测器在运行时。cycledetector被认为是一个可选组件;虽然它默认包含在内,但在构建时可以使用--without-cycle-gc选项在Unix平台(包括Mac OS X)上 configure 脚本中禁用它。如果以这种方式禁用循环检测器,则gc模块将不可用.

 

1.10.1。Python中的引用计数

有两个宏Py_INCREF(x)Py_DECREF(x),它们处理引用计数的递增和递减。Py_DECREF()当计数达到零时,也会对象进行alsofrees。为了灵活性,它不直接调用free() – 而是通过对象的type object中的函数指针进行调用。为了这个目的(和其他人),每个对象都包含一个指向其类型对象的指针.

现在最大的问题仍然是:何时使用Py_INCREF(x)Py_DECREF(x)?让我们先介绍一些术语。没有人“拥有”一个物体;但你可以own a reference到一个对象。对象的引用计数现在定义为对其拥有的引用数。参考的所有者负责致电Py_DECREF()何时不再需要参考。可以转移参考的所有权。有三种方式可以使用自己的引用:传递,存储或调用Py_DECREF()。忘记处理自己的引用会造成内存泄漏.

也有可能borrow[2]对象的引用。参考书目的人不应该叫Py_DECREF()。借款人不得持有比借给它的所有者更长的时间。在业主处置之后使用借来的参考可能会使用freedmemory,应该完全避免[3] .

借用而不是拥有一个引用的优点是你不需要小心处理通过代码在所有可能路径上的引用 – 换句话说,借用引用你不会冒出现过早退出泄漏的风险被采取。借用拥有的缺点是,在一些微妙的情况下,在看似正确的代码中借用的参考可以在借用它的所有者实际上用于它之后使用.

借用的引用可以通过调用Py_INCREF()。这不会影响从中借用参考的所有者的状态 – 它会创建一个新的自有参考,并赋予更多的责任(新的所有者必须妥善处理参考,以及前任所有者).

 

1.10.2。所有权规则

无论何时将对象引用传入或传出函数,它都是函数接口规范的一部分,是否所有权都是通过引用传递的.

返回对象引用的大多数函数都会通过引用传递所有权。特别是,所有功能,它的功能是创建一个新对象,如PyLong_FromLong()Py_BuildValue(),传递给接收者。即使对象实际上不是新对象,您仍然接受对该对象的新引用的所有权。例如,PyLong_FromLong()维护一个流行值的缓存,并且可以返回对缓存项的引用.

从其他对象中提取对象的许多函数也会转移对引用的所有权,例如PyObject_GetAttrString()。然而,这里的图片不太清楚,因为一些常见的例程是例外:PyTuple_GetItem(), PyList_GetItem(), PyDict_GetItem()PyDict_GetItemString()所有返回你从thetuple,list或dictionary中借用的引用.

功能 PyImport_AddModule()也会返回一个借来的引用,尽管它实际上可能会创建它返回的对象:这是可能的,因为对象的对象引用存储在sys.modules.

当你将对象引用传递给另一个函数时,通常,函数借用了您的参考 – 如果需要存储它,它将使用Py_INCREF()成为独立所有者。这个规则有两个重要的例外:PyTuple_SetItem()PyList_SetItem()。这些函数接管传递给它们的项目的所有权 – 即使它们失败了!(注意PyDict_SetItem()和朋友们不接管所有权 – 他们是“正常的”。)

当从Python调用C函数时,它借用来自调用者的参数的引用。调用者拥有对该对象的引用,因此在函数返回之前保证借用的引用的生命周期。只有在必须存储或传递这种明确的引用时,才能通过调用Py_INCREF().

从Python调用的C函数返回的对象引用必须是一个拥有的引用 – 所有权从函数传递给它的调用者

 

1.10.3。薄冰

在某些情况下,使用借来的参考资料看似无害的导致问题。这些都与解释器的隐式调用有关,这可能导致引用的所有者处理它.

第一个也是最重要的一个例子就是使用Py_DECREF() onan无关的对象借用对列表项的引用。例如:

void  bug(PyObject *list)  {      PyObject *item = PyList_GetItem(list, 0);        PyList_SetItem(list, 1, PyLong_FromLong(0L));      PyObject_Print(item, stdout, 0); /* BUG! */  }

这个函数首先借用对list[0]的引用,然后用值list[1]替换0,最后打印借来的引用。看起来无害, 对?但事实并非如此!

让我们按照控制流程进入PyList_SetItem()。该列表拥有对其所有项目的引用,因此当替换项目1时,它必须处理原始项目1.现在让我们假设原始项目1是用户定义的类的实例,并且让我们进一步假设该类定义了一个__del__()方法。如果这个类实例的引用计数为1,处理它将调用它__del__()方法

由于它是用Python编写的,__del__()方法可以执行arbitraryPython代码。它可能会做一些事情来使item中的bug()的引用无效吗?你打赌!假设传入bug()的列表可以被__del__()方法,它可以执行对del list[0],并假设这是该对象的最后一个引用,它将释放与之关联的内存,从而使失效item.

一旦您知道问题的根源,解决方案就很容易:暂时增加引用计数。该函数的正确版本为:

void  no_bug(PyObject *list)  {      PyObject *item = PyList_GetItem(list, 0);        Py_INCREF(item);      PyList_SetItem(list, 1, PyLong_FromLong(0L));      PyObject_Print(item, stdout, 0);      Py_DECREF(item);  }

这是一个真实的故事。旧版本的Python包含了这个bug的变种,有人在C调试器中花了相当多的时间来弄清楚他的__del__()方法会失败…

借用参考的第二个问题是涉及线程的变体。通常,Python解释器中的多个线程无法相互影响,因为有一个全局锁保护Python的整个对象空间。但是,可以使用宏Py_BEGIN_ALLOW_THREADS临时释放此锁,并使用Py_END_ALLOW_THREADS重新获取它。这在阻塞I / O调用时很常见,其他线程在等待I / O完成时使用处理器。显然,以下函数与前一个函数有相同的问题:

void  bug(PyObject *list)  {      PyObject *item = PyList_GetItem(list, 0);      Py_BEGIN_ALLOW_THREADS      ...some blocking I/O call...      Py_END_ALLOW_THREADS      PyObject_Print(item, stdout, 0); /* BUG! */  }

 

1.10.4。NULL指针

通常,将对象引用作为参数的函数不希望你传递它们NULL指针,如果你这样做,将转储核心(或导致后来的核心转储)。返回对象引用的函数通常只返回NULL以指示发生了异常。不测试NULL参数的原因是函数经常将它们接收到的对象传递给其他函数 – 如果每个函数都要测试NULL,那么会有很多冗余测试和代码会运行得更慢.

当收到一个可能是NULL的指针时,最好仅在“source:”处测试NULL,例如malloc()或来自可能引发异常的函数.

Py_INCREF()Py_DECREF()不检查NULL指针 – 但是,他们的变种Py_XINCREF()Py_XDECREF()do.

用于检查特定对象类型的宏(Pytype_Check())不检查NULL指针 – 再次,有很多代码连续调用其中的几个来测试对象的各种不同的预期类型,这将产生冗余测试。没有变种NULLchecking.

C函数调用机制保证传递给Cfunctions的参数列表(在示例中args)永远不会NULL – 实际上它保证它总是一个元组[4] .

NULL指针“逃避”给Python用户是一个严重的错误.

 

1.11。用C ++编写扩展名

可以用C ++编写扩展模块。一些限制适用。如果主程序(Python解释器)由C编译器编译和链接,则不能使用具有构造函数的全局或静态对象。如果主程序由C ++编译器链接,这不是问题。必须使用extern "C"。没有必要在extern "C" {...}中包含Python头文件 – 如果定义了符号__cplusplus(所有最近的C ++编译器都定义了这个符号),它们就会使用这个正式的.

 

1.12。为扩展模块提供C API

许多扩展模块只提供要从thePython使用的新函数和类型,但有时扩展模块中的代码对于其他扩展模块非常有用。例如,扩展模块可以实现类型“集合”,其类似于没有顺序的列表。就像标准Pythonlist类型有一个允许扩展模块创建和操作列表的C API一样,这个新的集合类型应该有一组C函数用于从其他扩展模块直接操作.

第一眼看来这很容易:只是写功能(没有声明它们static,当然),提供适当的头文件,并记录C API。事实上,如果所有扩展模块始终与Python解释器静态链接,这将有效。但是,当模块用作共享库时,一个模块中定义的符号可能对另一个模块不可见。可见性的细节取决于操作系统;somesystems使用一个全局命名空间用于Python解释器和所有扩展模块(例如Windows),而其他人则需要在模块链接时明确列出导入符号(AIX是一个示例),或者提供不同策略的选择(大多数Unices)。即使符号全局可见,也可能尚未加载要调用其功能的模块!

因此,可移植性不需要对符号可见性做任何假设。这意味着应该声明扩展模块中的所有符号static,除了模块的初始化函数,以避免与其他扩展模块的名称冲突(如模块的方法表和初始化函数)。这意味着should可从其他扩展模块访问的符号必须以不同的方式导出.

Python提供了一种特殊的机制,可以将C级信息(指针)从一个扩展模块传递到另一个扩展模块:Capsules。Capsule是一种存储指针的Python数据类型(void *)。胶囊只能通过其C API创建和访问,但它们可以像任何其他Python对象一样传递。特别是,可以将它们分配给扩展模块的名称空间中的名称。然后其他扩展模块可以导入该模块,检索该名称的值,然后从Capsule中检索指针.

胶囊可用于导出扩展模块的C API的方法有很多种。每个函数都可以获得自己的Capsule,或者所有C API指针都可以存储在一个数组中,该数组的地址发布在Capsule中。并且存储和检索指针的各种任务可以在提供代码的模块和客户端模块之间以不同的方式分布.

无论选择哪种方法,正确命名胶囊都很重要。功能PyCapsule_New()采用名称参数(const char *);你被允许传递一个NULL名字,但我们强烈建议你指定一个名字。正确命名的Capsules提供一定程度的运行时类型安全性;没有可行的方法告诉一个unnamedCapsule来自另一个.

特别是,用于公开C API的Capsules应该按照这个惯例给出一个名字:

modulename.attributename

方便功能PyCapsule_Import()通过Capsule提供的C API很容易,但只有当Capsule的名称与此约定匹配时才可以。这种行为为C API用户提供了很高的确定性,即他们加载的Capsule包含正确的C API .

下面的示例演示了一种方法,该方法将大部分负担放在导出模块的编写器上,这适用于常用的库模块。它存储了void指针成为胶囊的价值。与模块对应的头文件提供了一个宏,负责导入模块并检索其C API指针;客户端模块只需在访问C API之前调用该宏.

导出模块是spam模块的修改,来自简单示例。函数spam.system()不直接调用C库函数system(),而是函数PySpam_System(),这当然会做一些更复杂的现实(比如在每个命令中添加“垃圾邮件”)。这个函数PySpam_System()也导出到其他扩展模块.

函数PySpam_System()是一个普通的C函数,声明static就像其他一切:

static int  PySpam_System(const char *command)  {      return system(command);  }

函数spam_system()以微不足道的方式修改:

static PyObject *  spam_system(PyObject *self, PyObject *args)  {      const char *command;      int sts;        if (!PyArg_ParseTuple(args, "s", &command))          return NULL;      sts = PySpam_System(command);      return PyLong_FromLong(sts);  }

在模块的开头,在

#include "Python.h"

行之后,必须再添加两行:

#define SPAM_MODULE#include "spammodule.h"

#define用于告诉头文件它是否包含在导出模块中,而不是客户端模块中。最后,模块的初始化函数必须注意初始化C API指针数组:

PyMODINIT_FUNC  PyInit_spam(void)  {      PyObject *m;      static void *PySpam_API[PySpam_API_pointers];      PyObject *c_api_object;        m = PyModule_Create(&spammodule);      if (m == NULL)          return NULL;        /* Initialize the C API pointer array */      PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;        /* Create a Capsule containing the API pointer array's address */      c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);        if (c_api_object != NULL)          PyModule_AddObject(m, "_C_API", c_api_object);      return m;  }

注意PySpam_API声明static;否则当PyInit_spam()终止时,pointerarray会消失!

大部分工作都在头文件spammodule.h中,看起来像这样:

#ifndef Py_SPAMMODULE_H  #define Py_SPAMMODULE_H  #ifdef __cplusplus  extern "C" {  #endif    /* Header file for spammodule */    /* C API functions */  #define PySpam_System_NUM 0  #define PySpam_System_RETURN int  #define PySpam_System_PROTO (const char *command)    /* Total number of C API pointers */  #define PySpam_API_pointers 1      #ifdef SPAM_MODULE  /* This section is used when compiling spammodule.c */    static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;    #else  /* This section is used in modules that use spammodule's API */    static void **PySpam_API;    #define PySpam_System    (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])    /* Return -1 on error, 0 on success.   * PyCapsule_Import will set an exception if there's an error.   */  static int  import_spam(void)  {      PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);      return (PySpam_API != NULL) ? 0 : -1;  }    #endif    #ifdef __cplusplus  }  #endif    #endif /* !defined(Py_SPAMMODULE_H) */

所有这一切客户端模块必须要做的才能访问函数PySpam_System()是在初始化函数中调用函数(或者说是宏)import_spam()

PyMODINIT_FUNC  PyInit_client(void)  {      PyObject *m;        m = PyModule_Create(&clientmodule);      if (m == NULL)          return NULL;      if (import_spam() < 0)          return NULL;      /* additional initialization can happen here */      return m;  }

这种方法的主要缺点是文件spammodule.h更复杂。但是,导出的每个功能的基本结构都是一样的,所以只需要学习一次.

最后应该提到的是,Capsules提供了额外的功能,这对于内存分配和Capsule中指针的释放特别有用。详细信息在Python / C API ReferenceManual的胶囊和胶囊的实现(文件Include/pycapsule.hObjects/pycapsule.c在Python源码分发中).

脚注

[1] 这个功能的界面已经存在于标准模块中os – 它被选为一个简单明了的例子.
[2] “借用”参考文献的比喻并不完全正确:所有者都有参考文献的副本.
[3] 检查引用计数是否至少为1 不起作用– 干扰计数本身可以在释放的内存中,因此可以在另一个对象中重复使用!
[4] 当你使用“旧式”调用约定时,这些保证不成立 – 这个仍然存在于现有的许多代码中.

文章导航

  • 1.1。一个简单的例子
  • 1.2。Intermezzo:错误和例外
  • 1.3。回到例子
  • 1.4。模块的方法表和初始化函数
  • 1.5。编译和链接
  • 1.6。从C
  • 1.7调用Python函数。在扩展函数中提取参数
  • 1.8。扩展函数的关键字参数
  • 1.9。建立任意值
  • 1.10。参考计数
  • 1.10.1。Python中的引用计数
  • 1.10.2。所有权规则
  • 1.10.3。薄冰
  • 1.10.4。NULL指针
  • 1.11。用C ++编写扩展名
  • 1.12。为扩展模块提供C API
本站所发布的一切资源仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如果侵犯你的利益,请发送邮箱到 [email protected],我们会很快的为您处理。
超哥软件库 » 1.使用C或C ++扩展Python – 扩展和嵌入Python解释器(Python教程)(参考资料)