Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a complete Python wrapper around a C Struct using Cython?

I am writing a a high level interface for a C library for Python using Cython.
I have an extension Type A that initializes the library with a pointer to a more complex C context structure c_context. The pointer is saved in A.
A also has a def function which in turn creates another extension Type B initializing another C structure with a library function call. This structure is needed for the subsequent library calls made in B.
B needs the c_context pointer from A which is wrapped by me within the extension type py_context in order to pass it to __cinit__ from B:

#lib.pxd (C library definitions)
cdef extern from "lib.h":
    ctypedef struct c_context:
        pass

#file py_context.pxd
from lib cimport c_context

cdef class py_context:
    cdef c_context *context
    cdef create(cls, c_context *context)
    cdef c_context* get(self)

#file py_context.pyx
def class py_context:
    @staticmethod
    cdef create(cls, c_context *c):   
        cls = py_nfc_context()  
        cls.context = c  
        return cls

    cdef c_context* get(self):
        return self.context

Passing the wrapper with the correct C context works perfectly.

Now I need to get the C struct out of py_context again and save it in B. I added cdef c_context get(self) to py_context.pxd/pyx. Calling py_context.get() from Bs __cinit__ results in: AttributeError: py_context object has no attribute get.

It seems like I do not get my head around when to call cdef functions in Cython.

So my question is: What is the best way to extract the C struct from my wrapper class again?

like image 507
a.Dippel Avatar asked Jun 23 '16 07:06

a.Dippel


People also ask

How do you write Cython in Python?

To make your Python into Cython, first you need to create a file with the . pyx extension rather than the . py extension. Inside this file, you can start by writing regular Python code (note that there are some limitations in the Python code accepted by Cython, as clarified in the Cython docs).

Does Cython compile to C?

The Cython language is a superset of Python that compiles to C, yielding performance boosts that can range from a few percent to several orders of magnitude, depending on the task at hand. For work that is bound by Python's native object types, the speedups won't be large.

What is Cimport in Python?

The cimport statement is used by Cython to import definitions from a . pxd file. This is different than using a normal Python import statement, which would load a regular Python module.

Is Cython compiled?

Cython is a compiled language that is typically used to generate CPython extension modules.


1 Answers

The trouble is that Cython doesn't know the data type of your py_context variable at compile time. Calls to cdef functions are resolved at compile time and there exists no mechanism to figure it out at runtime by attribute lookup (as with normal Python functions).

[Note that def functions written within Cython are still compiled and can specify data types, so are perfectly capable of calling cdef functions if they have the right information.]

You don't give the relevant code where it's going wrong (the constructor of type B), but here's an very simplified example which will hopefully give you a couple of ways to fix it:

cdef class A:
    cdef f(self):
        return

def f1(var):
    var.f()

#f1(A()) # will fail at runtime with an attribute error

In f1 the type of var is unknown, and thus you can't call cdef functions.

def f2(A var):
    var.f()

f2(A()) # will work
f2(1) # will fail, int can't be converted to A

In f2 the type of var is constrained to be A, and therefore it can happily call cdef functions associated with A. If you pass something that isn't A to it you'll get a TypeError at runtime.

def f3(var):
    cdef A another_reference_to_var = var # this does test that the types match
    another_reference_to_var.f()

f3(A()) # will work
f3(1) # will fail, int can't be converted to A

The function f3 can take a variable of any type. However, when you assign it to another_reference_to_var which is cdefed to be an A it checks that the type matches (and raises a runtime exception if it doesn't). Since another_reference_to_var is known to be A at compile time, you can call As cdef functions.

Essentially, you need to specify the type of the relevant input to your __cinit__ function.

like image 134
DavidW Avatar answered Oct 18 '22 15:10

DavidW