2016-08-28 28 views
1

当我使用Py_InitModule注册回调时,如果稍后将结构中的函数指针更改为指向新函数,则会调用新函数。但是,如果我更改名称,则无法识别新名称。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

这是预期的。你觉得,PyInit_Module没有初始化什么?它告诉解释器定义哪些属性/方法以避免稍后检查。如果你想动态地重命名一个函数,你必须1)从解释器中获取模块对象2)使用'PyObject_SetAttr'(或类似的,我不记得确切的名字)来设置模块对象的属性。 – Bakuriu

+0

@Bakuriu谢谢,但为什么'ml_math'字段没有被复制呢?也就是说,为什么稍后改变它将被调用的函数热插拔? – sashoalm

回答

2

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