如何将Python对象存储在Cython C++容器中?

17
我希望将使用的现有库移植到Python中,其中C++库采用。 在这种情况下,它是adevs library
问题是如何在C++容器中使用Cython存储Python对象? 我知道这在某种程度上不鼓励,因为存在引用计数的问题,但是否仍然可以完成,并且如果可以,如何完成?
我是一名有用的助手,能够翻译文本。

我知道Gauthier Boaglios' answer对于一个类似的问题的回答。但是,这并没有解决引用计数的问题,就像我尝试以下代码:

假设在'cadevs.pxd'中我有以下代码:

cdef extern from "adevs/adevs_digraph.h" namespace "adevs":
cdef cppclass PortValue[VALUE, PORT]:
    PortValue() except +
    PortValue(PORT port, const VALUE& value) except +
    PORT port
    VALUE value

而在'adevs.pyx'中:

from cpython.ref cimport PyObject
cimport cadevs

ctypedef PyObject* PythonObject

cdef class PortValue:
    cdef cadevs.PortValue[PythonObject, PythonObject]* _c_portvalue

    def __cinit__(self, object port, object value):
        self._c_portvalue = new cadevs.PortValue[PythonObject, PythonObject](
            <PyObject *>port, <PyObject *>value
        )

    def __dealloc__(self):
        del self._c_portvalue

    property port:
        def __get__(self):
            return <object>self._c_portvalue.port

    property value:
        def __get__(self):
            return <object>self._c_portvalue.value

然后我使用cython进行编译。
$ cython --cplus -3 adevs.pyx
$ g++ -shared -pthread -fPIC -fwrapv -O2 -Wall -I/usr/include/python3.4m -I../include -lpython3.4m -o adevs.so adevs.cpp

但在Python或IPython中运行

import adevs
pv = adevs.PortValue((1,2), 3)
pv.port
pv.port

当元组(1,2)的引用丢失时,会导致Python崩溃。


2
我建议使用boost::python,它比原始的Python C-API提供了更好和更直接的C++集成。 - πάντα ῥεῖ
我强烈推荐使用pybind11 https://pybind11.readthedocs.io/en/latest/ 这个库使得绑定C++代码变得非常容易。 - Patrick
2个回答

2

你说得对,如果在Cython中使用C++容器存储Python对象来运行内存安全的应用程序,会遇到困难。如果你想在Cython中实现这个功能,而不是像Mike MacNeil的回答中提到的Pybind11,那么你有几种选择。

  1. 将值存储在Cython/Python中的某个位置,以保持引用计数在对象在容器中时大于1。例如:
cdef class PortValue:
    cdef cadevs.PortValue[PythonObject, PythonObject]* _c_portvalue

    # Add fields to keep stored Python objects alive.
    cdef object port_ref_holder 
    cdef object value_ref_holder 


    def __cinit__(self, object port, object value):
        self._c_portvalue = new cadevs.PortValue[PythonObject, PythonObject](
            <PyObject *>port, <PyObject *>value
        )

        # Assign objects here to keep them alive.
        port_ref_holder = port
        value_ref_holder = value
  1. 您可以使用Python C-API,结合Wrapper手动增加和减少引用。Python C API参考计数的文档在这里。cython包会自动将此API作为Cython声明(.pxd)文件提供给您,在代码中通过cimport即可使用(请参阅这里)。我可以在单独的C ++文件中添加引用计数功能,或者我可以根据与C接口的指南直接将此代码原样添加到Cython中。以下是一个示例:
from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF
cdef extern from *:
    """
    class PyRef {
        PyObject* obj;
    public:
        
        PyObject* get() {return obj;}
        PyRef() {obj = NULL;}
        PyRef(PyObject* set_obj) {
            Py_XINCREF(set_obj); 
            obj = set_obj;}
        
        ~PyRef() {
            Py_XDECREF(obj);obj = NULL;
        }

        PyRef(const PyRef& other)  {
            Py_XINCREF(other.obj); 
            obj = other.obj;
        }
        PyRef(PyRef&& other) {obj = other.obj; other.obj = NULL;}

        PyRef& operator=(const PyRef& other) {
            Py_XDECREF(obj); 
            Py_XINCREF(other.obj); 
            obj = other.obj;
            return *this;
        }
        PyRef& operator=(PyRef&& other) {
            Py_XDECREF(obj); 
            obj = other.obj; 
            other.obj = NULL;
            return *this;
        }
    };
    """
    cdef cppclass PyRef:
        PyRef() except +
        PyRef(PyObject* set_obj) except +
        PyObject* get() except +
    

如果要借用存储的Python对象,请使用“PyRef”类而不是PythonObject,并使用其get()方法。


0

我不确定绑定到Cython可能会很困难,我建议使用Pybind11,它使编写C++的Python包装器(参见this particular example中的面向对象代码)相对简单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接