Как написать полную оболочку Python вокруг C Struct с помощью Cython?

Я пишу интерфейс высокого уровня для библиотеки C для Python с помощью Cython.
У меня есть расширение Type A, которое инициализирует библиотеку указателем на более сложную структуру контекста C c_context. Указатель сохраняется в A.
A также имеет функцию def, которая, в свою очередь, создает другое расширение. Тип B инициализирует другую структуру C с вызовом функции библиотеки. Эта структура необходима для последующих вызовов библиотеки, сделанных в B.
B требуется указатель c_context из A, который завернут мной в тип расширения py_context, чтобы передать его в __cinit__ из 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

Передача обертки с правильным контекстом C работает отлично.

Теперь мне нужно снова вернуть C struct из py_context и сохранить его в B. Я добавил cdef c_context get(self) в py_context.pxd/pyx. Вызов py_context.get() из Bs __cinit__ приводит к: AttributeError: py_context object has no attribute get.

Кажется, что я не обнимаюсь, когда называть функции cdef в Cython.

Итак, мой вопрос:. Каков наилучший способ извлечения C-структуры из класса оболочки?

Ответ 1

Проблема в том, что Cython не знает тип данных вашей переменной py_context во время компиляции. Вызов функций cdef разрешается во время компиляции, и нет механизма для его определения во время выполнения путем поиска атрибута (как и с обычными функциями Python).

[Обратите внимание, что функции def, написанные в Cython, все еще скомпилированы и могут указывать типы данных, поэтому они вполне способны вызывать функции cdef, если у них есть правильная информация.]

Вы не укажете соответствующий код, где это происходит неправильно (конструктор типа B), но здесь очень упрощенный пример, который, надеюсь, даст вам пару способов исправить его:

cdef class A:
    cdef f(self):
        return

def f1(var):
    var.f()

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

В f1 тип var неизвестен, и поэтому вы не можете вызывать функции cdef.

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

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

В f2 тип var ограничен A, и поэтому он может с радостью вызвать функции cdef, связанные с A. Если вы передадите то, что не соответствует A, вы получите TypeError во время выполнения.

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

Функция f3 может принимать переменную любого типа. Однако, когда вы назначаете его another_reference_to_var, который cdef ed должен быть A, он проверяет, соответствует ли тип (и вызывает исключение во время выполнения, если это не так). Поскольку another_reference_to_var, как известно, A во время компиляции, вы можете вызвать функции A cdef.

По существу, вам нужно указать тип соответствующего ввода для вашей функции __cinit__.