带有类方法的Cython回调 我开始使用Cython来将一些C++代码与Python接口。由于我需要在C++中做一些异步数据获取,所以我计划使用C++代码中的回调到Python。到目前为止,我已经成功地从C++调用Python函数,但是,我不能从C++调用Python类方法。我在StackOverflow上看到过一些关于它的问题,但是没有人能为一件看似简单的事情提供一个简单的答案。这是一个工作玩具的例子
myclass.h:带有类方法的Cython回调 我开始使用Cython来将一些C++代码与Python接口。由于我需要在C++中做一些异步数据获取,所以我计划使用C++代码中的回调到Python。到目前为止,我已经成功地从C++调用Python函数,但是,我不能从C++调用Python类方法。我在StackOverflow上看到过一些关于它的问题,但是没有人能为一件看似简单的事情提供一个简单的答案。这是一个工作玩具的例子,python,c++,methods,callback,cython,Python,C++,Methods,Callback,Cython,myclass.h: \ifndef MYCLASS\u H #定义MYCLASS_H #定义PY_-SSIZE_-T_-CLEAN #包括 typedef void(*callbackfun)(PyObject*obj); 类MyClass { 公众: void start(callbackfun user_func,PyObject*obj); }; #endif//MYCLASS\u H myclass.cpp: #包括“myclass.h” void MyClass::start(ca
\ifndef MYCLASS\u H
#定义MYCLASS_H
#定义PY_-SSIZE_-T_-CLEAN
#包括
typedef void(*callbackfun)(PyObject*obj);
类MyClass
{
公众:
void start(callbackfun user_func,PyObject*obj);
};
#endif//MYCLASS\u H
myclass.cpp:
#包括“myclass.h”
void MyClass::start(callbackfun user_func,PyObject*obj)
{
用户函数(obj);
}
PyMyClass.pyx:
<代码> diditul:语言= C++
#cython:language_level=3
“myclass.h”中的cdef外部程序:
ctypedef void(*callbackfun)(对象对象对象)
cdef cppclass MyClass:
MyClass()
无效开始(调用运行用户函数,对象对象对象)
cdef无效回调(自):
打印('回拨')
cdef类PyMyClass:
cdef MyClass*c_MyClass_ptr
定义(自我):
self.c_myClass_ptr=new myClass()
def uu dealoc uu(自我):
del self.c_myClass_ptr
def启动(自):
self.c_myClass_ptr.start(回调,self)
main.py:
从PyMyClass导入PyMyClass
def main():
myClass=PyMyClass()
myClass.start()
如果名称=“\uuuuu main\uuuuuuuu”:
main()
现在,如果我将PyMyClass.pyx
中的callback
函数转换为一个方法,如下所示:
PyMyClass.pyx:
<代码> diditul:语言= C++
#cython:language_level=3
“myclass.h”中的cdef外部程序:
ctypedef void(*callbackfun)(对象对象对象)
cdef cppclass MyClass:
MyClass()
无效开始(调用运行用户函数,对象对象对象)
cdef类PyMyClass:
cdef MyClass*c_MyClass_ptr
定义(自我):
self.c_myClass_ptr=new myClass()
def uu dealoc uu(自我):
del self.c_myClass_ptr
cdef无效回调(自):
打印('回拨')
def启动(自):
self.c_myClass_ptr.start(self.callback,self)
现在编译器抱怨:
Compiling PyMyClass.pyx because it changed.
[1/1] Cythonizing PyMyClass.pyx
Error compiling Cython file:
------------------------------------------------------------
...
cdef void callback(self):
print('Called back')
def start(self):
self.c_myClass_ptr.start(self.callback, self) ^
------------------------------------------------------------
PyMyClass.pyx:24:36: Cannot assign type 'void (PyMyClass)' to 'callbackfun'
Traceback (most recent call last):
File "setup.py", line 22, in <module>
setup(ext_modules = cythonize(ext_modules))
File "/Users/andreac/anaconda3/lib/python3.7/site-packages/Cython/Build/Dependencies.py", line 1097, in cythonize
cythonize_one(*args)
File "/Users/andreac/anaconda3/lib/python3.7/site-packages/Cython/Build/Dependencies.py", line 1220, in cythonize_one
raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: PyMyClass.pyx
正在编译PyMyClass.pyx,因为它已更改。
[1/1]Cythonizing PyMyClass.pyx
编译Cython文件时出错:
------------------------------------------------------------
...
cdef无效回调(自):
打印('回拨')
def启动(自):
self.c_myClass_ptr.start(self.callback,self)^
------------------------------------------------------------
PyMyClass.pyx:24:36:无法将类型“void(PyMyClass)”分配给“callbackfun”
回溯(最近一次呼叫最后一次):
文件“setup.py”,第22行,在
设置(ext_模块=cythonize(ext_模块))
cythonize中的文件“/Users/andreac/anaconda3/lib/python3.7/site packages/Cython/Build/Dependencies.py”,第1097行
cythonize_one(*args)
cythonize_one中的文件“/Users/andreac/anaconda3/lib/python3.7/site packages/Cython/Build/Dependencies.py”,第1220行
raise编译器错误(无,pyx_文件)
Cython.Compiler.Errors.CompileError:PyMyClass.pyx
这个问题有(简单的)解决方法吗?(是的,我知道使用
PyObject*
)还有一个潜在的引用计数问题。您有一些问题,但它们相对容易解决:
self.callback
正在尝试用self
指针绑定函数。我不能100%确定在这种情况下它是如何工作的——它最终可能会创建一个可调用的Python对象,但无论哪种方式,它都肯定与回调函数不匹配。将其更改为&PyMyClass.callback
。我还添加了一个&
,因为Cython似乎需要它来了解情况
&PyMyClass.callback
仍然与callbackfun
的签名不匹配callbackfun
需要一个泛型Python对象,而PyMyClass.callback
的第一个参数被专门化为指向PyMyClass
的底层C结构的指针。这是很容易解决的一个演员,给予
self.c_myClass_ptr.start(<callbackfun>&PyMyClass.callback, self)
self.c_myClass_ptr.start(&PyMyClass.callback,self)
这种对基于PyObject
的结构的转换是使用pythoncapi时的常见习惯用法,因此很好。不幸的是,强制转换最终可能会隐藏错误,所以要小心self
。您在问题中提到了这一点,但我将再次重复,以防将来的读者错过它(正如我在第一次浏览时所做的那样)。在这种情况下,您可以立即使用self
,然后丢弃它。但是,如果你将它保存在C++代码中,那么它可能会被破坏,而C++代码则会有一个有效的指针。设置回调时,这确实需要一个Py_INCREF
,取消设置回调时需要一个Py_DECREF
。确保这种情况发生可能很难做到正确