3

I am fairly new to Cython, so this is probably fairly trivial, but I haven't been able to find the answer anywhere.

I've defined a struct type and I want to write a function that will initialize all the fields properly and return a pointer to the new struct.

from cpython.mem import PyMem_Malloc


ctypedef struct cell_t:
    DTYPE_t[2] min_bounds
    DTYPE_t[2] max_bounds
    DTYPE_t size

    bint is_leaf
    cell_t * children[4]

    DTYPE_t[2] center_of_mass
    UINT32_t count


cdef cell_t * make_cell(DTYPE_t[2] min_bounds, DTYPE_t[2] max_bounds):
    cdef cell_t * cell = <cell_t *>PyMem_Malloc(sizeof(cell_t)) # <- Fails here
    if not cell:
        MemoryError()

    cell.min_bounds[:] = min_bounds
    cell.max_bounds[:] = max_bounds
    cell.size = min_bounds[0] - max_bounds[0]
    cell.is_leaf = True
    cell.center_of_mass[:] = [0, 0]
    cell.count = 0

    return cell

However, when I try to compile this, I get the following two errors during compilation:

cdef cell_t * make_cell(DTYPE_t[2] min_bounds, DTYPE_t[2] max_bounds):
    cdef cell_t * cell = <cell_t *>PyMem_Malloc(sizeof(cell_t))
                        ^
Casting temporary Python object to non-numeric non-Python type
------------------------------------------------------------

cdef cell_t * make_cell(DTYPE_t[2] min_bounds, DTYPE_t[2] max_bounds):
    cdef cell_t * cell = <cell_t *>PyMem_Malloc(sizeof(cell_t))
        ^
Storing unsafe C derivative of temporary Python reference
------------------------------------------------------------

Now, I've looked all over, and from what I can gather, cell is actually stored in a temporary variable that gets deallocated at the end of the function.

Any help would be greatly appreciated.

Pavlin
  • 5,390
  • 6
  • 38
  • 51

1 Answers1

4
cell.min_bounds = min_bounds

This doesn't do what you think it does (although I'm not 100% sure what it does do). You need to copy arrays element by element:

cell.min_bounds[0] = min_bounds[0]
cell.min_bounds[1] = min_bounds[1]

Same for max_bounds.

The line that I suspect is giving you that error message is:

cell.center_of_mass = [0, 0]

This is trying to assign a Python list to a C array (remembering that arrays and pointers are somewhat interchangeable in C), which doesn't make much sense. Again, you'd do

cell.center_of_mass[0] = 0
cell.center_of_mass[1] = 0

All this is largely consistent with the C behaviour that there aren't operators to copy whole arrays into each other, you need to copy element by element.


Edit:

However that's not your immediate problem. You haven't declared PyMem_Malloc so it's assumed to be a Python function. You should do

from cpython.mem cimport PyMem_Malloc

Make sure it's cimported, not imported


Edit2:

The following compiles fine for me:

from cpython.mem cimport PyMem_Malloc

ctypedef double DTYPE_t

ctypedef struct cell_t:
    DTYPE_t[2] min_bounds
    DTYPE_t[2] max_bounds


cdef cell_t * make_cell(DTYPE_t[2] min_bounds, DTYPE_t[2] max_bounds) except NULL:
    cdef cell_t * cell = <cell_t *>PyMem_Malloc(sizeof(cell_t))
    if not cell:
        raise MemoryError()
    return cell

I've cut down cell_t a bit (just to avoid having to make declarations of UINT32_t). I've also given the cdef function an except NULL to allow it to signal an error if needed and added a raise before MemoryError(). I don't think either of these changes are directly related to your error.

DavidW
  • 29,336
  • 6
  • 55
  • 86
  • Nice catch, I could probably get this to a single line using something like some equivallent memcopy I guess. However, I should've marked where it fails, and it is not this. Please see my updated question. I tried removing this logic altogether and I got the same error. – Pavlin Feb 26 '18 at 20:19
  • Sorry, but I didn't include this in the question - I thought it was clear that I included it, the errors would also be much different if I had forgotten to include it. Sorry for all this confusion. – Pavlin Feb 26 '18 at 20:29
  • I've posted a shortened version of your code that compiles fine for me. If that still doesn't work I think you need to post the complete code that's failing – DavidW Feb 26 '18 at 20:39
  • 1
    @Pavlin : It's almost always worth going to the trouble of creating a [mcve]. This allows people to be clear *exactly* what you have included etc. Do note the "Minimal" there. In this case, your structure doesn't need more than one member. – Martin Bonner supports Monica Feb 26 '18 at 20:44
  • @MartinBonner I thought my example was pretty small and clear, so I left it as-is. @DavidW I am so sorry, I was just being stupid and didn't check how I imported `cpython.mem`. I had done it using `import` whereas I should've done it using `cimport`. I guess this is one thing that will take some getting used to. Thanks so much! I was completely stumped. – Pavlin Feb 26 '18 at 20:50