I would like to port an existing c++ library with cython to Python, with the C++ library employing templates. In this case, it is the adevs library.
The question is how can I store Python objects in a C++ container with Cython? I know this is somehow discouraged, for issues of reference counting, but can it be done nevertheless and if so, how?
I am aware of Gauthier Boaglios' answer to a similar question. However, this does not address the issue of reference counting, apparently, as I tried the following:
Let's say in 'cadevs.pxd' I have the following code:
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
And in '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
Then I cythonize and compile
$ 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
But running in python or ipython
import adevs
pv = adevs.PortValue((1,2), 3)
pv.port
pv.port
crashes Python as the reference to the (1, 2) tuple is lost, apparently.
You are right in that you will have difficulties with running a memory-safe application by storing python objects in a C++ container with Cython. If you want to do this in Cython, and not Pybind11 (as referenced by Mike MacNeil's answer), then you have a number of options.
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
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 +
Then you use the "PyRef" class instead of PythonObject and use its get() method to borrow a reference to the stored python object.
I'm not sure about binding to Cython which may be difficult, I would recommend using Pybind11, which makes it (relatively) simple to write python wrappers for C++ (see this particular example for object oriented code).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With