9

I have an array of float values that is created in regular Python, that I want to pass to a cython function that fronts for an underlying C function. The C function requires the array to be passed as a floating pointer as in:

void setOverlays(const float * verts);

the cython wrapper looks like this:

def set_overlays(verts):
    setOverlays(verts)

How can I make verts into a cython array? I thought that this might work:

cdef float * cVerts = [v for v in verts]

but unfortunately the value generated is a Python object and in this case automatic conversion does not work.

The equivalent expression(that works) in ctypes is:

cVerts = (c_float * len(verts))()
for i in range(len(verts)):
    cVerts[i] = verts[i]
setOverlays(cast(byteref(cVerts), POINTER(c_float)))

I am trying to achieve the same thing, but in cython

Thanks in advance!

Kiril
  • 2,091
  • 7
  • 33
  • 43

5 Answers5

5

I believe you can do this by iterating over the python list of floats and putting them in a C array.

cimport cython
from libc.stdlib cimport malloc, free

cdef:
    float * cfloats
    int i

cfloats = <float *> malloc(len(pyfloats)*cython.sizeof(float))
if cfloats is NULL:
  raise MemoryError()
for i in xrange(len(pyfloats)):
  cfloats[i] = pyfloats[i]
setOverlays(cfloats)
free(cfloats)
Alex
  • 18,332
  • 10
  • 49
  • 53
EfForEffort
  • 55,816
  • 4
  • 36
  • 41
  • 1
    You're forgetting your null pointer check/`MemoryError` raise. http://docs.cython.org/src/tutorial/clibraries.html#id7 – JAB Jul 27 '12 at 14:54
5

The accepted answer is wrong and leads to a segmentation fault, because the memory for the float * is never allocated.

The answer of @JAB shows the way to go, but I would like to elaborate more.

Passing an array:

The question is how to convert python array to an c-style array. Python array (from the array module) is a wrapper around continuous memory, so there is no need to copy the memory - we just can pass the pointer to the c-function:

from cpython cimport array
import array

def set_overlays(array.array verts):
    setOverlays(verts.data.as_floats)

Using array module is better than numpy because it is part of the standard library - there is no need to install something. This solution is short and nice, but has a problem: somebody could use it with an int-array and there will be no error - the memory just gets reinterpreted. Sometimes it is what one wants, but most of the time this is not the case.

To make sure, that the passed array-object has the right type of data, memory views can be used:

from cpython cimport array #still needed

def set_overlays_memview(float[::1] verts):
    setOverlays(&verts[0])

[::1] ensures, that the memory view wraps around continuous memory. However, this code has a problem if there are no elements in the memory view,because of the out-of-bounds-access verts[0]. The right way to handle it depends on the function setOverlays and is not a part of this answer.

Passing a list:

If we have to pass a python list to the c-function, we have to copy the values to a continuous memory. Best done using the functionality of array- there is no need to reinvent the wheel:

from cpython cimport array
import array

def set_overlays_list(list verts):
    cdef array.array a =  array.array('f', verts)
    setOverlays(a.data.as_floats) #we already know they are floats
ead
  • 32,758
  • 6
  • 90
  • 153
3

Check out http://docs.cython.org/src/userguide/memoryviews.html. Looks like it also gives tips on how to utilize Python's built-in array module, as well as numpy.

JAB
  • 20,783
  • 6
  • 71
  • 80
  • 1
    Unfortunately Cython-0.15 doesn't support C99 variable length arrays. I haven't checked Cython-0.16. – Maxim Egorushkin Jul 27 '12 at 14:44
  • 1
    Ah, I see. ...Nice to see it finally supports Python 3.x, though, which wasn't the case the last time I felt like trying out Cython. – JAB Jul 27 '12 at 14:52
-1

This is what I use for preparing arrays for passing to Cython, or C/CPP with SWIG.

import numpy as np    
def make_c_array(a):
    """
    Take an input numpy array and convert to being ready for use in C.
    """
    b = []
    for i in range(len(a)):
        b.append(a[i])
    a = np.array(b,dtype=np.dtype('d'),order='C')
    return a
Kyle Mede
  • 103
  • 1
  • 7
-2

I found the Cython specific way:

cdef float* cVerts = []
    for i in xrange(len(verts)):
        cVerts[i] = verts[i]
setOverlays(cVerts)

Interestingly enough the above two solutions were rejected by the cython compiler.

Kiril
  • 2,091
  • 7
  • 33
  • 43
  • 2
    I believe this is incorrect -- you never specified the size of cVerts and I'm fairly sure you'll need to free that memory later. When it actually runs you'll be writing to random memory at best. If you have a documentation link which says otherwise I'd be interested to see it, as many difficult things would need to be interpreted by Cython at compile time (and I don't think it does those things). – Pyrce Jan 26 '13 at 01:04
  • The compiler rejected it due to a typo in the import statement for malloc. Try "EfForEffort" answer again and it should work. – Alex Nov 27 '13 at 00:43
  • 1
    you might want to use something like cdef float cVerts[len(verts)] as your solution seems to just overwrite memory (watch out for segmentation faults!) – Yannick Versley May 31 '14 at 12:40