python - 带有类方法的 Cython 回调

标签 python c++ methods callback cython

我开始使用 Cython 将一些 C++ 代码与 Python 连接起来。由于我需要在 C++ 中进行一些异步数据采集,因此我计划使用从我的 C++ 代码到 Python 的回调。到目前为止,我已经能够从 C++ 中成功调用 Python 函数,但是,我无法从 C++ 中调用 Python 类方法。我在 StackOverflow 上看到了几个关于它的问题,但没有人为看似简单的事情提供简单的答案。这是一个工作玩具示例。

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

我的类.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)

ma​​in.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 指针绑定(bind)。我不是 100% 确定它在这种情况下是如何工作的——它可能最终会创建一个可调用的 Python object,但无论哪种方式,它肯定与你想要的回调函数不匹配。将其更改为 &PyMyClass.callback。我还添加了 &,因为 Cython 似乎需要它来了解情况。

  2. &PyMyClass.callback 仍然不匹配 callbackfun 的签名。 callbackfun 需要一个通用的 Python 对象,而 PyMyClass.callback 的第一个参数专门用作指向 PyMyClass 的底层 C 结构的指针。这很容易通过转换解决,给出

    self.c_myClass_ptr.start(<callbackfun>&PyMyClass.callback, self)
    

    这种与基于 PyObject 的结构的转换是使用 Python C API 的常见习惯用法,所以很好。不幸的是,转换最终可能会隐藏错误,所以要小心。

  3. 您正在制造潜在的引用计数灾难:您将“借用引用”传递给 self。你在问题中提到了这一点,但我会重复一遍,以防 future 的读者错过它(就像我在第一次浏览时所做的那样)。在这种情况下没问题——您立即使用 self 然后丢弃它。但是,如果您将它保存在您的 C++ 代码中,那么它可能会被破坏,从而为 C++ 代码留下一个有效的指针。当设置回调时,这确实需要一个 Py_INCREF ,当取消设置回调时需要一个 Py_DECREF 。确保这种情况发生可能很难做到正确......

关于python - 带有类方法的 Cython 回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57875047/

相关文章:

C++将二维数组对象插入另一个二维数组对象

PHP静态方法递归

python - tensorflow的mod运算符可以匹配python的modulo实现吗?

python - 通过迭代给定数据返回字典列表

python - MySQL 'SHOW TABLES' 返回计数而不是列表 (Python)

c++ - 如何将 RapidJSON 文档序列化为字符串?

python - 在 Python 中转义 HTML

c++ - 是否有一些用于模拟 Glib::Dispatcher 的 Boost 功能?

java - 缺少子类的方法实现时的标准异常?

java - 如何调用另一个类的方法?