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

时间:2016-08-28 16:23:44

标签: python c++ python-embedding

当我使用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调用重新注册它。但与此同时,更改名称并没有这种行为。

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

这是故意行为,还是一些疏忽?文档中没有提到我能找到的有关PyMethodDef生命周期的任何内容。

1 个答案:

答案 0 :(得分:2)

您看到的不一致性来自函数代码(函数本身的属性)与从模块调用它的名称之间的区别,模块是模块的属性(其dict中的键) 。虽然函数的名称也存储在函数对象中,但它仅用于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可调用。