Assuming I know what I am doing, I try to initialize a tuple from a C array of doubles:
from cpython.ref cimport PyObject
from cpython.tuple cimport PyTuple_New
from cpython.float cimport PyFloat_FromDouble
cdef extern from "Python.h":
struct PyTupleObject:
PyObject *ob_item[1]
# ======================================================================
# Tuple utility API
# ======================================================================
cdef tuple from_doubles(unsigned n, const double* buffer):
""" Create a tuple from an array of floats.
"""
cdef tuple t = PyTuple_New(n)
cdef PyObject** items = (<PyTupleObject*>t).ob_item
cdef unsigned i
for i in range(n):
items[i] = <PyObject*>PyFloat_FromDouble(buffer[i])
return t
From my understanding, PyFloat_FromDouble
creates a new object, which is stolen by the tuple when it is stored in items[i]
. This is what PyTuple_SET_ITEM
does but without the redundant type checking. Still as far as I understand, no Py_INCREF
/Py_DECREF
calls are needed here.
From the doc:
Few functions steal references; the two notable exceptions are PyList_SetItem() and PyTuple_SetItem(), which steal a reference to the item (but not to the tuple or list into which the item is put!). These functions were designed to steal a reference because of a common idiom for populating a tuple or list with newly created objects; for example, the code to create the tuple (1, 2, "three") could look like this (forgetting about error handling for the moment; a better way to code this is shown below):
PyObject *t; t = PyTuple_New(3); PyTuple_SetItem(t, 0, PyLong_FromLong(1L)); PyTuple_SetItem(t, 1, PyLong_FromLong(2L)); PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));
However, when I compile that code with Cython 0.26.1, I get the following errors:
Error compiling Cython file:
------------------------------------------------------------
...
"""
cdef tuple t = PyTuple_New(n)
cdef PyObject** items = (<PyTupleObject*>t).ob_item
cdef unsigned i
for i in range(n):
items[i] = <PyObject*>PyFloat_FromDouble(buffer[i])
^
------------------------------------------------------------
fin/tuplex.pyx:19:19: Casting temporary Python object to non-numeric non-Python type
Error compiling Cython file:
------------------------------------------------------------
...
"""
cdef tuple t = PyTuple_New(n)
cdef PyObject** items = (<PyTupleObject*>t).ob_item
cdef unsigned i
for i in range(n):
items[i] = <PyObject*>PyFloat_FromDouble(buffer[i])
^
------------------------------------------------------------
fin/tuplex.pyx:19:19: Storing unsafe C derivative of temporary Python reference
How could I fix my code to properly store the newly created PyFloat
in the tuple?
I had to provide my own external definition for PyFloat_FromDouble
in order to avoid all kinds of automatic reference management from Cython:
from cpython.ref cimport PyObject
from cpython.tuple cimport PyTuple_New
cdef extern from "Python.h":
PyObject* PyFloat_FromDouble(double v)
# Above:
# Do NOT use the definition from `cpython.float` to avoid
# automatic DECREF calls.
ctypedef struct PyTupleObject:
PyObject *ob_item[1]
By defining PyFloat_FromDouble
as returning a PyObject*
instead of an object
as it is done in cpython.float
Py_DECREF
call, andFor reference here is the remaining code:
# ======================================================================
# Tuple utility API
# ======================================================================
cdef tuple from_doubles(unsigned n, const double* buffer):
""" Create a tuple from an array of floats.
"""
cdef tuple t = PyTuple_New(n)
cdef PyObject** items = (<PyTupleObject*>t).ob_item
cdef unsigned i
for i in range(n):
items[i] = PyFloat_FromDouble(buffer[i])
return t
def test():
cdef double[1000] values = list(range(1000))
return from_doubles(1000, values)