Python C扩展-为什么可调用的C函数必须接受参数并返回PyObject*
我刚刚开始使用Python C扩展,我很好奇为什么从Python调用的C函数必须接受2个PyObject*参数并返回一个PyObject*。我编写了以下“Hello World”扩展:Python C扩展-为什么可调用的C函数必须接受参数并返回PyObject*,python,c,python-c-extension,Python,C,Python C Extension,我刚刚开始使用Python C扩展,我很好奇为什么从Python调用的C函数必须接受2个PyObject*参数并返回一个PyObject*。我编写了以下“Hello World”扩展: #include <Python.h> static PyObject * hello_world(PyObject *self, PyObject *noargs) { printf("Hello World\n"); return Py_BuildValue(""); } //
#include <Python.h>
static PyObject *
hello_world(PyObject *self, PyObject *noargs)
{
printf("Hello World\n");
return Py_BuildValue("");
}
// Module functions table.
static PyMethodDef
module_functions[] = {
{ "hello_world", hello_world, METH_NOARGS, "hello world method" },
{ NULL }
};
// This function is called to initialize the module.
PyMODINIT_FUNC
inittesty2(void)
{
Py_InitModule("testy2", module_functions);
}
因为Python函数,甚至只是打印到标准输出的小函数,都不仅仅是C函数的包装器 在最简单的情况下,考虑Python中的内省功能。Python函数是一个完整的对象,您可以查询:
>>> def hello():
... print 'hello'
...
>>> dir(hello)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
当然,您可以想象一个只包装C函数的扩展工具。
检查一下,它允许您为许多脚本语言(包括Python)编写扩展。因为它是最低公分母,所以它可以让你像你的
hello\u world
那样包装一个函数,但当然你也会失去很多功能。关于各种PyObject
指针,有几点要说
PyErr...
函数以设置特定异常后,才能执行此操作。)
这还意味着,每当您不想抛出异常时,您必须返回指向某个realPyObject
的指针。如果函数没有特别要返回的内容,只需返回Py\u None
(最好使用Py\u return\u None
宏来正确获取引用计数)或“true”(使用Py\u RETURN\u TRUE
)PyObject*self
指向调用函数的对象,或者指向它所属的模块实例。请注意,您定义的每个函数要么是类方法,要么是模块方法。没有完全独立的函数PyObject*args
指向函数参数(可能是一个元组或多个参数的列表)。你说得对,一个不带任何参数的函数不应该需要这个,而且,据我所知,你是对的。你不必定义它;您可以简单地将函数定义为
static PyObject *PyMyClass_MyFunc(PyObject *self) {
/* ..do something.. */
Py_RETURN_TRUE;
}
对于您定义的数据类型,当您将其放入PyMethodDef
时,您仍然必须将其强制转换为PyCFunction
,但我相信只要使用METH\u NOARGS
标志,强制转换是安全的但请注意以下可能存在的风险评论。static PyObject *PyMyClass_Func(PyObject *self, PyObject *args, PyObject *kwds)
{
/*...*/
}
第三个参数用于命名的可选参数。在这种情况下,您也必须将函数指针强制转换为PyCFunction
,但如果您在方法表中为函数设置了正确的标志(METH\u关键字
),这也是安全的模块级函数的第一个参数是模块对象。在C中定义类时(这里的方法使用相同的
PyMethodDef
结构),第一个参数是实例(类似于Python中的self
)
当使用METH\u NOARGS
时,Python将传递NULL
作为第二个参数。他们可以用一个参数将它转换成一个函数,但我猜他们认为不需要
返回值很容易解释。每个Python函数都有一个返回值。如果在Python中没有显式使用return
,函数将返回None
当然,在C语言中,必须明确返回值,因此如果不使用它,就必须自己返回None
。Python为此提供了一个宏:
Py_RETURN_NONE;
或者,您可以自己访问全局None
实例:
Py_INCREF(Py_None);
return Py_None;
但是宏更容易使用
您可能认为返回
NULL
应等同于None
,但NULL
用于指示函数引发了异常。它是Py\u None
,而不是PyNone
。还有Py\u RETURN\u NONE
类似于Py\u RETURN\u TRUE
和Py\u RETURN\u FALSE
。对于(3),如果我错了,请纠正我,但是如果代码使用的是调用约定,即所有参数都在堆栈上传递,被调用方(函数)将它们从堆栈中弹出,删除未使用的参数会导致崩溃,因为Python将始终传递NULL作为第二个参数,而函数不会弹出它。是的,这是因为在您可能正在使用的cdecl
中,参数从右向左推到堆栈上,并由调用方弹出。因此,堆栈上额外的NULL
将被函数忽略。这并不意味着它将适用于任何其他通话约定。例如,它不会用于stdcall
。只要可移植性不是OP的问题,我想这是可以的。如果是的话,我不会跳过args。@yak好的,谢谢你指出这一点。可能确实有一个我没有意识到的风险。我编辑过这篇文章;现在它考虑到了您的评论。值得一提的是,(3)在我尝试时起作用,但当我尝试在模块函数数组中声明'arg less'函数时,编译器确实给了我以下警告—“警告:从不兼容的指针类型初始化[默认启用]”
Py_INCREF(Py_None);
return Py_None;