模块方法表和初始化函数
我承诺过要向大家展示如何从 Python 程序中调用 spam_system()。 首先,我们需要在“方法表”中列出它的名称和地址:
static PyMethodDef SpamMethods[] = {
...
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
...
{NULL, NULL, 0, NULL} /* Sentinel */
};
注意第三个参数 ( METH_VARARGS ) ,这个标志指定会使用C的调用惯例。可选值有 METH_VARARGS 、 METH_VARARGS | METH_KEYWORDS 。值 0 代表使用 PyArg_ParseTuple() 的陈旧变量。
如果单独使用 METH_VARARGS ,函数会等待Python传来tuple格式的参数,并最终使用 PyArg_ParseTuple() 进行解析。
如果应当将关键字参数传给该函数则可以在第三个字段中设置 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_FUNC
PyInit_spam(void)
{
return PyModule_Create(&spammodule);
}
请注意 PyMODINIT_FUNC 将函数声明为 PyObject * 返回类型,声明了平台所要求的任何特殊链接声明,并针对于= C++ 将函数声明为 extern "C"。
当 Python 程序首次导入 spam 模块时,PyInit_spam() 将被调用。 (有关嵌入 Python 的注释参见下文。) 它将调用 PyModule_Create(),该函数会返回一个模块对象,并基于在模块定义中找到的表(一个 PyMethodDef 结构体的数组)将内置函数对象插入到新创建的模块中。 PyModule_Create() 返回一个指向它所创建的模块对象的指针。 它可能会会程度严重的特定错误而中止,或者在模块无法成功初始化时返回 NULL。 初始化函数必须将模块对象返回给其调用者,以便将其插入到 sys.modules 中。
当嵌入 Python 时,除非 PyImport_Inittab 表中有条目,否则不会自动调用 PyInit_spam() 函数。 要将模块添加到初始化表中,请使用 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 */
if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
fprintf(stderr, "Error: could not extend in-built modules table\n");
exit(1);
}
/* Pass argv[0] to the Python interpreter */
Py_SetProgramName(program);
/* Initialize the Python interpreter. Required.
If this step fails, it will be a fatal error. */
Py_Initialize();
/* Optionally import the module; alternatively,
import can be deferred until the embedded script
imports it. */
PyObject *pmodule = PyImport_ImportModule("spam");
if (!pmodule) {
PyErr_Print();
fprintf(stderr, "Error: could not import module 'spam'\n");
}
...
PyMem_RawFree(program);
return 0;
}
备注 要从 sys.modules 删除实体或导入已编译模块到一个进程里的多个解释器(或使用 fork() 而没用 exec() )会在一些扩展模块上产生错误。扩展模块作者可以在初始化内部数据结构时给出警告。
更多关于模块的现实的例子包含在Python源码包的 Modules/xxmodule.c 中。这些文件可以用作你的代码模板,或者学习。脚本 modulator.py 包含在源码发行版或Windows安装中,提供了一个简单的GUI,用来声明需要实现的函数和对象,并且可以生成供填入的模板。脚本在 Tools/modulator/ 目录。查看README以了解用法。
备注 不像我们的 spam 例子, xxmodule 使用了 多阶段初始化 (Python3.5开始引入), PyInit_spam 会返回一个 PyModuleDef 结构体,然后创建的模块放到导入机制。细节参考 PEP 489 的多阶段初始化。 |