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

Py_InitModule copies the name but not the function pointer?

当我使用 Py_InitModule 注册回调时,如果我稍后将结构中的函数指针更改为指向新函数,则会调用新函数。但是如果我改了名字,新名字就不能识别了

#include <Python.h>

PyObject* foo1(PyObject *self, PyObject *args)
{
    printf("foo1\n");
    Py_RETURN_NONE;
}

PyObject* foo2(PyObject *self, PyObject *args)
{
    printf("foo2\n");
    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 foo\n");
    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;
}

这给出了以下输出:

foo1
foo2
foo2
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'module' object has no attribute 'foo2'

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

到目前为止,我非常确定 PyMethodDef 需要在 python 代码尝试调用方法时存活(即不能是 stack/local 变量),但只使用了函数指针本身。

这是故意行为还是疏忽?该文档没有提及我能找到的有关 PyMethodDef 生命周期的任何信息。

您看到的不一致是由于函数代码(即函数本身的 属性)与从模块中调用它的名称(即 属性 模块(在其字典中输入)。虽然函数名称也存储在函数对象中,但它仅用于 repr 而不是函数的基础 属性。

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

可以使用常规 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 和相关函数被设计为使用静态分配的结构来调用,创建副本将浪费 space.不复制它们解释了为什么更改 ml_meth 中的实际 C 回调会更改 Python 可调用对象。