带有类方法的 Cython 回调
Posted
技术标签:
【中文标题】带有类方法的 Cython 回调【英文标题】:Cython callback with class method 【发布时间】:2019-09-10 16:39:48 【问题描述】:我开始使用 Cython 将一些 C++ 代码与 Python 连接起来。由于我需要在 C++ 中进行一些异步数据采集,因此我计划使用从 C++ 代码到 Python 的回调。到目前为止,我已经能够成功地从 C++ 调用 Python 函数,但是,我不能从 C++ 调用 Python 类方法。我在 *** 上看到过几个关于它的问题,但没有人为看似简单的事情提供简单的答案。这是一个工作玩具示例。
myclass.h:
#ifndef MYCLASS_H
#define MYCLASS_H
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef void (*callbackfun)(PyObject* obj);
class MyClass
public:
void start(callbackfun user_func, PyObject* obj);
;
#endif // MYCLASS_H
myclass.cpp:
#include "myclass.h"
void MyClass::start(callbackfun user_func, PyObject* obj)
user_func(obj);
PyMyClass.pyx:
# distutils: language = c++
# cython: language_level=3
cdef extern from "myclass.h":
ctypedef void (*callbackfun)(object obj)
cdef cppclass MyClass:
MyClass()
void start(callbackfun user_func, object obj)
cdef void callback(self):
print('Called back')
cdef class PyMyClass:
cdef MyClass *c_myClass_ptr
def __cinit__(self):
self.c_myClass_ptr = new MyClass()
def __dealloc__(self):
del self.c_myClass_ptr
def start(self):
self.c_myClass_ptr.start(callback, self)
main.py:
from PyMyClass import PyMyClass
def main():
myClass = PyMyClass()
myClass.start()
if __name__ == "__main__":
main()
现在,如果我将PyMyClass.pyx
中的callback
函数转换为方法,如下:
PyMyClass.pyx:
# distutils: language = c++
# cython: language_level=3
cdef extern from "myclass.h":
ctypedef void (*callbackfun)(object obj)
cdef cppclass MyClass:
MyClass()
void start(callbackfun user_func, object obj)
cdef class PyMyClass:
cdef MyClass *c_myClass_ptr
def __cinit__(self):
self.c_myClass_ptr = new MyClass()
def __dealloc__(self):
del self.c_myClass_ptr
cdef void callback(self):
print('Called back')
def start(self):
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
是否有(简单)解决此问题的方法? (是的,我知道使用PyObject*
也存在潜在的引用计数问题)
【问题讨论】:
【参考方案1】:您有一些问题,但解决起来相对简单:
self.callback
正在尝试将函数与self
指针绑定。我不能 100% 确定在这种情况下它是如何工作的——它最终可能会创建一个可调用的 Python object
,但无论哪种方式,它都绝对不符合你想要的回调函数。将其更改为&PyMyClass.callback
。我还添加了&
,因为 Cython 似乎需要它来了解情况。
&PyMyClass.callback
仍然与callbackfun
的签名不匹配。 callbackfun
需要一个通用 Python 对象,而 PyMyClass.callback
的第一个参数专门作为指向 PyMyClass
的底层 C 结构的指针。这很容易通过演员来解决,给
self.c_myClass_ptr.start(<callbackfun>&PyMyClass.callback, self)
这种对基于PyObject
的结构的转换是使用Python C API 的常见习惯用法,所以很好。不幸的是,强制转换最终会隐藏错误,所以要小心。
你有一个潜在的引用计数灾难正在发生:你将一个“借来的引用”传递给self
。您在问题中提到了这一点,但我会重申以防未来的读者错过它(就像我在第一次浏览时所做的那样)。在这种情况下,这很好 - 您立即使用 self
然后丢弃它。但是,如果您将其保存在您的 C++ 代码中,那么它可能会被破坏,从而使 C++ 代码具有有效的指针。设置回调时确实需要Py_INCREF
,而取消设置回调时需要Py_DECREF
。确保这种情况发生可能很难做到……
【讨论】:
以上是关于带有类方法的 Cython 回调的主要内容,如果未能解决你的问题,请参考以下文章
使用 python-c-api 调用的 Cython 回调段错误
如何在 python 包装中使用 unicode 字符串用于带有 cython 的 c++ 类?