Clase de extensión Cython: ¿Cómo expongo los métodos en la estructura C generada automáticamente?

Tengo un código C ++ existente que define algunas clases que necesito usar, pero necesito poder enviar esas clases al código Python. Específicamente, necesito crear instancias de clase en C ++, crear objetos de Python para servir como envoltorios para estos objetos de C ++, luego pasar estos objetos de Python al código de Python para su procesamiento. Esto es solo una parte de un progtwig más grande de C ++, por lo que debe hacerse en última instancia en C ++ utilizando la API de C / Python.

Para hacer mi vida más fácil, he usado Cython para definir clases de extensión (clases cdef) que sirven como envoltorios de Python para mis objetos C ++. Estoy utilizando el formato típico donde la clase cdef contiene un puntero a la clase C ++, que luego se inicializa cuando se crea la instancia de la clase cdef. Ya que también quiero poder reemplazar el puntero si tengo un objeto C ++ existente para envolver, he agregado métodos a mis clases cdef para accept() el objeto C ++ y tomar su puntero. Mis otras clases cdef usan con éxito el método accept() en Cython, por ejemplo, cuando un objeto es dueño de otro.

Aquí hay una muestra de mi código Cython:

MyCPlus.pxd

 cdef extern from "MyCPlus.h" namespace "mynamespace": cdef cppclass MyCPlus_Class: MyCPlus_Class() except + 

PyModule.pyx

 cimport MyCPlus from libcpp cimport bool cdef class Py_Class [object Py_Class, type PyType_Class]: cdef MyCPlus.MyCPlus_Class* thisptr cdef bool owned cdef void accept(self, MyCPlus.MyCPlus_Class &indata): if self.owned: del self.thisptr self.thisptr = &indata self.owned = False def __cinit__(self): self.thisptr = new MyCPlus.MyCPlus_Class() self.owned = True def __dealloc__(self): if self.owned: del self.thisptr 

El problema viene cuando trato de acceder al método accept() desde C ++. Intenté usar las palabras clave public y api en mi clase cdef y en el método accept() , pero no puedo descubrir cómo exponer este método en la estructura C del archivo .h generado automáticamente por Cython. No importa lo que intente, la estructura C se parece a esto:

PyModule.h (generado automáticamente por Cython)

 struct Py_Class { PyObject_HEAD struct __pyx_vtabstruct_11PyModule_Py_Class *__pyx_vtab; mynamespace::MyCPlus_Class *thisptr; bool owned; }; 

También intenté escribir la self como una Py_Class , e incluso intenté declarar hacia adelante Py_Class con las palabras clave public y api . También experimenté haciendo accept() un método estático. Nada que haya intentado funciona para exponer el método accept() modo que pueda usarlo desde C ++. __pyx_vtab acceder a él a través de __pyx_vtab , pero obtuve un error del comstackdor, “uso no válido de tipo incompleto”. He buscado bastante, pero no he visto una solución para esto. ¿Alguien puede ayudarme? ¡Por favor y gracias!

Como señaló en su comentario , parece que el miembro __pyx_vtab es solo para uso de Cython, ya que ni siquiera define el tipo de estructura para él en el encabezado (s) exportado (s).

Agregando a su respuesta, un enfoque también podría ser:

 cdef api class Py_Class [object Py_Class, type Py_ClassType]: ... cdef void accept(self, MyCPlus.MyCPlus_Class &indata): ... # do stuff here ... cdef api void (*Py_Class_accept)(Py_Class self, MyCPlus.MyCPlus_Class &indata) Py_Class_accept = &Py_Class.accept 

Básicamente, definimos un puntero de función y lo configuramos al método de la extensión que queremos exponer. Esto no es muy diferente a la función cdef ‘d de su respuesta; la principal diferencia sería que podemos definir nuestros métodos como de costumbre en la definición de clase sin tener que duplicar la funcionalidad o las llamadas de método / función a otra función para exponerla. Una advertencia es que tendríamos que definir la firma del puntero de nuestra función casi literalmente a la del método, además del tipo de extensión del self (en este caso) y etc .; de nuevo, esto también se aplica a las funciones regulares.

Tenga en cuenta que probé esto en un archivo .pyx Cython de nivel .pyx , no lo he hecho y no bash probar en un archivo de implementación de CPP. Pero espero que esto funcione tan bien, supongo.

Esto no es realmente una solución, pero se me ocurrió una solución para mi problema. Todavía estoy esperando una solución que me permita decirle a Cython que exponga el método accept() a C ++.

Mi solución es que escribí una función separada para mi clase de Python (no un método). Luego di la palabra clave api tanto a mi clase de Python como a la nueva función:

 cdef api class Py_Class [object Py_Class, type PyType_Class]: (etc.) cdef api Py_Class wrap_MyCPlusClass(MyCPlus.MyCPlus_Class &indata): wrapper = Py_Class() del wrapper.thisptr wrapper.thisptr = &indata wrapper.owned = False return wrapper 

Esto se vuelve un poco difícil de manejar con la cantidad de clases diferentes que necesito ajustar, pero al menos Cython coloca la función en la API donde es fácil de usar:

 struct Py_Class* wrap_MyCPlusClass(mynamespace::MyCPlusClass &); 

Probablemente desee usar cpdef lugar de cdef al declarar accept . Ver los documentos :

Callable desde Python y C
* Se declaran con la sentencia cpdef.
* Se puede llamar desde cualquier lugar, ya que utiliza un poco de magia Cython.
* Utiliza las convenciones de llamada de C más rápidas cuando se llama desde otro código de Cython.

¡Trata eso!