Reputation: 93
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?
Upvotes: 9
Views: 1610
Reputation: 30899
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 cdef
ed 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 A
s cdef
functions.
Essentially, you need to specify the type of the relevant input to your __cinit__
function.
Upvotes: 6