Py_InitModule复制名称,但不复制函数指针

Py_InitModule copies the name but not the function pointer?

本文关键字:复制 函数 指针 InitModule Py      更新时间:2023-10-16

当我使用Py_InitModule注册回调时,如果我稍后更改结构中的函数指针以指向新函数,则调用新函数。但是,如果我更改了名称,则无法识别新名称。

#include <Python.h>
PyObject* foo1(PyObject *self, PyObject *args)
{
    printf("foo1n");
    Py_RETURN_NONE;
}
PyObject* foo2(PyObject *self, PyObject *args)
{
    printf("foo2n");
    Py_RETURN_NONE;
}
int main()
{
    PyMethodDef methods[] = {
        { "foo", foo1, METH_VARARGS, "foo" },
        { 0, 0, 0, 0 }
    };
    Py_Initialize();
    Py_InitModule("foo", methods);
    PyRun_SimpleString("import foon");
    PyRun_SimpleString("foo.foo()n");
    methods[0].ml_meth = foo2;
    PyRun_SimpleString("foo.foo()n");
    methods[0].ml_name = "foo2";
    PyRun_SimpleString("foo.foo()n");
    PyRun_SimpleString("foo.foo2()n");
    return 0;
}

输出如下:

<>以前foo1foo2foo2回溯(最近一次调用):文件",第1行AttributeError: 'module'对象没有'foo2'属性

这似乎是一个非常不一致的行为。我第一次遇到它是在我为PyMethodDef methods使用堆栈变量时,一旦变量超出作用域,程序就会崩溃,而我仍然试图从python调用c++回调。所以我测试了改变指针确实改变了哪个函数被调用,即使我没有用另一个Py_InitModule调用重新注册它。但同时,更改名称不具有此行为。

到目前为止,我很确定PyMethodDef需要活只要python代码试图调用方法(即不能是堆栈/局部变量),但只有函数指针本身被使用。

这是故意的行为,还是疏忽?文档中没有提到任何关于PyMethodDef lifetime的内容

您看到的不一致源于函数代码之间的差异,这是函数本身的属性,而从模块中调用它的名称是模块的属性(其字典中的键)。虽然函数的名称也存储在函数对象中,但它仅用于repr,并不是函数的基本属性。

这是有意为之的,因为它允许在不同的地方以不同的名称使用相同的函数对象——如果函数存储在容器中,甚至可以不使用名称。如果可以通过改变函数的属性来"重命名"它,这将是不可能的。

可以使用常规Python函数来演示相同的差异,如下所示:
>>> def add(a, b): return a + b
... 
>>> def sub(a, b): return a - b
... 
>>> add
<function add at 0x7f9383127938>  # the function has a name
>>> add.__name__ = 'foo'
>>> add                           # the name is changed, but...
<function foo at 0x7f9383127938>
>>> foo                           # the change doesn't affect the module
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
>>> add.__code__ = sub.__code__   # we can change the code, though
>>> add(2, 2)
0

至于你在评论中的问题:没有复制方法字段,因为Py_InitModule和相关函数被设计为用静态分配的结构调用,创建其副本将是浪费空间。不复制它们解释了为什么在ml_meth中更改实际的C回调会更改Python可调用对象。