code python derived-class python-2.x python-c-api

code - cpython



Cómo crear dinámicamente un tipo derivado en Python C-API (3)

Supongamos que tenemos el tipo Noddy como se define en el tutorial sobre cómo escribir los módulos de extensión C para Python . Ahora queremos crear un tipo derivado, sobrescribiendo solo el __new__() de Noddy .

Actualmente utilizo el siguiente enfoque (comprobación de errores eliminada para facilitar la lectura):

PyTypeObject *BrownNoddyType = (PyTypeObject *)PyType_Type.tp_alloc(&PyType_Type, 0); BrownNoddyType->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; BrownNoddyType->tp_name = "noddy.BrownNoddy"; BrownNoddyType->tp_doc = "BrownNoddy objects"; BrownNoddyType->tp_base = &NoddyType; BrownNoddyType->tp_new = BrownNoddy_new; PyType_Ready(BrownNoddyType);

Esto funciona, pero no estoy seguro de si es la forma correcta de hacerlo. Hubiera esperado que también tuviera que establecer el indicador Py_TPFLAGS_HEAPTYPE , porque Py_TPFLAGS_HEAPTYPE dinámicamente el objeto tipo en el montón, pero al hacerlo generé una segfault en el intérprete.

También pensé en llamar explícitamente a type() usando PyObject_Call() o similar, pero descarté la idea. Tendría que ajustar la función BrownNoddy_new() en un objeto de función de Python y crear una asignación de diccionario __new__ a este objeto de función, lo que parece una tontería.

¿Cuál es la mejor manera de hacerlo? ¿Mi enfoque es correcto? ¿Hay alguna función de interfaz que haya pasado por alto?

Actualizar

Hay dos hilos en un tema relacionado en la lista de correo de python-dev (1) (2) . De estos hilos y algunos experimentos, deduzco que no debo configurar Py_TPFLAGS_HEAPTYPE menos que el tipo sea asignado por una llamada a type() . Existen diferentes recomendaciones en estos subprocesos, ya sea que sea mejor asignar el tipo manualmente o llamar al type() . Estaría contento con esto último si tan solo supiera cuál es la forma recomendada de ajustar la función C que se supone que debe ir en la ranura tp_new . Para los métodos regulares, este paso sería fácil: podría usar PyDescr_NewMethod() para obtener un objeto envoltorio adecuado. Sin embargo, no sé cómo crear dicho objeto envoltorio para mi __new__() ; tal vez necesite la función no documentada PyCFunction_New() para crear dicho objeto envoltorio.


Encontré el mismo problema cuando estaba modificando una extensión para que sea compatible con Python 3, y encontré esta página cuando estaba tratando de resolverla.

Eventualmente lo resolví leyendo el código fuente para el intérprete de Python, PEP 0384 y la documentación para el C-API .

Establecer el indicador Py_TPFLAGS_HEAPTYPE le dice al intérprete que PyTypeObject su PyTypeObject como PyHeapTypeObject , que contiene miembros adicionales que también deben asignarse. En algún momento, el intérprete intenta referirse a estos miembros adicionales y, si los deja sin asignar, provocará un segfault.

Python 3.2 introdujo las estructuras C PyType_Slot y PyType_Spec y la función C PyType_FromSpec que simplifica la creación de tipos dinámicos. En pocas palabras, usa PyType_Slot y PyType_Spec para especificar los miembros tp_* de PyTypeObject y luego llama a PyType_FromSpec para hacer el trabajo sucio de asignar e inicializar la memoria.

Desde PEP 0384, tenemos:

typedef struct{ int slot; /* slot id, see below */ void *pfunc; /* function pointer */ } PyType_Slot; typedef struct{ const char* name; int basicsize; int itemsize; int flags; PyType_Slot *slots; /* terminated by slot==0. */ } PyType_Spec; PyObject* PyType_FromSpec(PyType_Spec*);

(Lo anterior no es una copia literal de PEP 0384, que también incluye a const char *doc como miembro de PyType_Spec . Pero ese miembro no aparece en el código fuente).

Para usar estos en el ejemplo original, supongamos que tenemos una estructura C, BrownNoddy , que extiende la estructura C para la clase base Noddy . Entonces tendríamos:

PyType_Slot slots[] = { { Py_tp_doc, "BrownNoddy objects" }, { Py_tp_base, &NoddyType }, { Py_tp_new, BrownNoddy_new }, { 0 }, }; PyType_Spec spec = { "noddy.BrownNoddy", sizeof(BrownNoddy), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots }; PyTypeObject *BrownNoddyType = (PyTypeObject *)PyType_FromSpec(&spec);

Esto debería hacer todo en el código original, incluida la llamada PyType_Ready , además de lo necesario para crear un tipo dinámico, incluida la configuración de Py_TPFLAGS_HEAPTYPE , y la asignación e inicialización de la memoria adicional para un PyHeapTypeObject .

Espero que sea útil.


Me pido disculpas de antemano si esta respuesta es terrible, pero puedes encontrar una implementación de esta idea en PythonQt , en particular, creo que los siguientes archivos pueden ser referencias útiles:

Este fragmento de PythonQtClassWrapper_init salta a la vista como algo interesante:

static int PythonQtClassWrapper_init(PythonQtClassWrapper* self, PyObject* args, PyObject* kwds) { // call the default type init if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) { return -1; } // if we have no CPP class information, try our base class if (!self->classInfo()) { PyTypeObject* superType = ((PyTypeObject *)self)->tp_base; if (!superType || (superType->ob_type != &PythonQtClassWrapper_Type)) { PyErr_Format(PyExc_TypeError, "type %s is not derived from PythonQtClassWrapper", ((PyTypeObject*)self)->tp_name); return -1; } // take the class info from the superType self->_classInfo = ((PythonQtClassWrapper*)superType)->classInfo(); } return 0; }

Vale la pena señalar que PythonQt usa un generador de envoltura, por lo que no está exactamente en línea con lo que estás pidiendo, pero personalmente creo que intentar engañar al vtable no es el diseño más óptimo. Básicamente, hay muchos generadores de envoltura C ++ diferentes para Python y las personas los usan por una buena razón: están documentados, hay ejemplos flotando en los resultados de búsqueda y en el desbordamiento de la pila. Si distribuyes una solución para esto que nadie ha visto antes, será mucho más difícil para ellos depurar si se topan con problemas. Incluso si es de código cerrado, el próximo tipo que tenga que mantenerlo se rascará la cabeza y tendrás que explicárselo a cada nueva persona que aparezca.

Una vez que obtenga un generador de código funcionando, todo lo que necesita hacer es mantener el código subyacente de C ++, no tiene que actualizar o modificar su código de extensión a mano. (Que probablemente no esté muy lejos de la tentadora solución con la que fuiste)

La solución propuesta es un ejemplo de romper la seguridad de tipo que la PyCapsule recientemente presentada proporciona un poco más de protección contra (cuando se usa como se indica).

Entonces, si bien es posible que no sea la mejor opción a largo plazo implementar derivadas / subclases de esta manera, sino más bien ajustar el código y dejar que el vtable haga lo que mejor hace y cuando el nuevo tipo tiene preguntas, puede simplemente señalarlo al documentación para la solution best fits .

Sin embargo, esta es solo mi opinión. :RE


Una forma de intentar entender cómo hacerlo es crear una versión de la misma utilizando SWIG. Vea lo que produce y vea si coincide o se hace de una manera diferente. Por lo que puedo decir, las personas que han estado escribiendo SWIG tienen una comprensión profunda de la extensión de Python. No puede doler ver cómo hacen las cosas de todos modos. Puede ayudarte a entender este problema.