Python C扩展-为什么可调用的C函数必须接受参数并返回PyObject*

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(""); } //

我刚刚开始使用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("");
}


// 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
指针,有几点要说

  • 作为返回类型所需的用于异常处理机制。具体来说,如果函数返回空指针,Python解释器将抛出异常。(只有在调用一个
    PyErr...
    函数以设置特定异常后,才能执行此操作。)

    这还意味着,每当您不想抛出异常时,您必须返回指向某个real
    PyObject
    的指针。如果函数没有特别要返回的内容,只需返回
    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;