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

时间:2014-11-14 19:12:22

标签: python c++ templates cython

我想将带有的现有库移植到Python,使用的C ++库。在这种情况下,它是adevs library

问题是我如何使用Cython将Python对象存储在C ++容器中?我知道这是discouraged,对于引用计数的问题,但是可以这样做,如果是的话,怎么做?

我知道Gauthier Boaglios' answersimilar question。但是,这并没有解决引用计数的问题,显然,我尝试了以下内容:

让我们说'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

然后我进行cythonize并编译

$ 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)元组的引用显然已经丢失了。

2 个答案:

答案 0 :(得分:2)

您是对的,因为通过使用 Cython 将 Python 对象存储在 C++ 容器中,您将难以运行内存安全应用程序。如果您想在 Cython 中执行此操作,而不是 Pybind11(如 Mike MacNeil 的回答所引用),那么您有多种选择。

  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 引用是 here。 cython 包在 Cython 中自动向您提供此 API,作为您可以导入的 cython 声明 (.pxd) 文件(请参阅 here)。我可以在单独的 C++ 文件中添加引用计数功能,或者我可以根据 Interfacing with C guide 将此代码直接添加到 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 +
    

然后您使用“PyRef”类而不是 PythonObject 并使用其 get() 方法借用对存储的 Python 对象的引用。

答案 1 :(得分:1)

我不确定要绑定到Cython可能会很困难,我建议使用Pybind11,这相对(相对)容易为C ++编写python包装器(有关面向对象的代码,请参见this particular example)。 / p>