Angenommen, wir haben den Typ Noddy
, der im Tutorial zum Schreiben von C-Erweiterungsmodulen für Python definiert ist . Jetzt wollen wir einen abgeleiteten Typ erstellen und nur die __new__()
Methode von überschreiben Noddy
.
Derzeit verwende ich den folgenden Ansatz (Fehlerprüfung auf Lesbarkeit gestrippt):
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);
Das funktioniert, aber ich bin mir nicht sicher, ob es der richtige Weg ist. Ich hätte erwartet, dass ich auch das Py_TPFLAGS_HEAPTYPE
Flag setzen muss, da ich das Typobjekt auf dem Heap dynamisch zuordne, dies jedoch zu einem Segfault im Interpreter führt.
Ich habe auch darüber nachgedacht, explizit type()
using PyObject_Call()
oder ähnliches aufzurufen , aber ich habe die Idee verworfen. Ich müsste die Funktion BrownNoddy_new()
in ein Python-Funktionsobjekt einbinden und eine Wörterbuchzuordnung __new__
zu diesem Funktionsobjekt erstellen , was albern erscheint.
Was ist der beste Weg, um dies zu erreichen? Ist mein Ansatz korrekt? Gibt es eine Schnittstellenfunktion, die ich verpasst habe?
Aktualisieren
Es gibt zwei Themen zu einem verwandten Thema in der Python-Dev-Mailingliste (1) (2) . Aus diesen Threads und einigen Experimenten schließe ich, dass ich nicht festlegen sollte, es Py_TPFLAGS_HEAPTYPE
sei denn, der Typ wird durch einen Aufruf an zugewiesen type()
. In diesen Threads gibt es unterschiedliche Empfehlungen, ob es besser ist, den Typ manuell zuzuweisen oder aufzurufen type()
. Ich würde mich über Letzteres freuen, wenn ich nur wüsste, wie die C-Funktion, die in den tp_new
Steckplatz eingefügt werden soll, empfohlen wird. Für reguläre Methoden wäre dieser Schritt einfach - ich könnte nur PyDescr_NewMethod()
ein geeignetes Wrapper-Objekt erhalten. Ich weiß jedoch nicht, wie ich ein solches Wrapper-Objekt für meine __new__()
Methode erstellen soll - möglicherweise benötige ich die undokumentierte Funktion PyCFunction_New()
, um ein solches Wrapper-Objekt zu erstellen.
quelle
__new__()
, um eine andere C ++ - Klasse zuzuweisen. Methoden müssen nicht überschrieben werden, da sie die virtuelle Elementfunktion aufrufen. Beachten Sie, dass ich dieses Problem inzwischen durch ein völlig anderes Design mit Vorlagentechniken gelöst habe. Die ursprüngliche Frage bleibt jedoch weiterhin bestehen.Antworten:
Ich habe das gleiche Problem festgestellt, als ich eine Erweiterung so geändert habe, dass sie mit Python 3 kompatibel ist, und habe diese Seite gefunden, als ich versucht habe, sie zu lösen.
Ich habe es schließlich gelöst, indem ich den Quellcode für den Python-Interpreter PEP 0384 und die Dokumentation für die C-API gelesen habe .
Durch Setzen des
Py_TPFLAGS_HEAPTYPE
Flags wird der Interpreter angewiesen, IhrPyTypeObject
as neu zu formulierenPyHeapTypeObject
, das zusätzliche Mitglieder enthält, die ebenfalls zugewiesen werden müssen. Irgendwann versucht der Interpreter, auf diese zusätzlichen Mitglieder zu verweisen. Wenn Sie sie nicht zuweisen, führt dies zu einem Segfault.In Python 3.2 wurden die C-Strukturen
PyType_Slot
undPyType_Spec
die C-Funktion eingeführtPyType_FromSpec
, die die Erstellung dynamischer Typen vereinfachen. Kurz gesagt, Sie verwendenPyType_Slot
undPyType_Spec
geben dietp_*
Mitglieder von anPyTypeObject
und rufen dannPyType_FromSpec
auf, um die schmutzige Arbeit des Zuweisens und Initialisierens des Speichers zu erledigen.Ab PEP 0384 haben wir:
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*);
(Das Obige ist keine wörtliche Kopie von PEP 0384, die auch
const char *doc
als Mitglied von enthalten istPyType_Spec
. Dieses Mitglied wird jedoch nicht im Quellcode angezeigt.)Um diese im ursprünglichen Beispiel zu verwenden, nehmen wir an, dass wir eine C-Struktur haben
BrownNoddy
, die die C-Struktur für die Basisklasse erweitertNoddy
. Dann hätten wir: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);
Dies sollte alles im ursprünglichen Code tun, einschließlich Aufrufen
PyType_Ready
, sowie das, was zum Erstellen eines dynamischen Typs erforderlich ist, einschließlich EinstellenPy_TPFLAGS_HEAPTYPE
und Zuweisen und Initialisieren des zusätzlichen Speichers für aPyHeapTypeObject
.Ich hoffe das ist hilfreich.
quelle
Ich entschuldige mich im Voraus , wenn diese Antwort schrecklich ist, aber Sie können eine Implementierung dieser Idee in PythonQt finden , insbesondere denke ich, dass die folgenden Dateien nützliche Referenzen sein könnten:
Dieses Fragment aus PythonQtClassWrapper_init ist für mich etwas interessant:
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; }
Es ist erwähnenswert, dass PythonQt einen Wrapper-Generator verwendet, sodass er nicht genau Ihren Anforderungen entspricht. Ich persönlich denke jedoch, dass der Versuch, die vtable zu überlisten, nicht das optimalste Design ist. Grundsätzlich gibt es viele verschiedene C ++ - Wrapper-Generatoren für Python, die aus gutem Grund verwendet werden. Sie sind dokumentiert, es gibt Beispiele in den Suchergebnissen und beim Stapelüberlauf. Wenn Sie eine Lösung dafür bereitstellen, die noch niemand zuvor gesehen hat, ist es für sie umso schwieriger, Fehler zu beheben, wenn sie auf Probleme stoßen. Selbst wenn es sich um eine geschlossene Quelle handelt, kratzt sich der nächste, der sie warten muss, am Kopf und Sie müssen es jeder neuen Person erklären, die mitkommt.
Sobald Sie einen Codegenerator zum Laufen gebracht haben, müssen Sie nur noch den zugrunde liegenden C ++ - Code pflegen. Sie müssen Ihren Erweiterungscode nicht mehr manuell aktualisieren oder ändern. (Was wahrscheinlich nicht zu weit von der verlockenden Lösung entfernt ist, mit der Sie sich entschieden haben)
Die vorgeschlagene Lösung ist ein Beispiel für die Aufhebung der Typensicherheit , gegen die die neu eingeführte PyCapsule etwas mehr Schutz bietet (bei bestimmungsgemäßer Verwendung).
Obwohl dies möglich ist, ist es möglicherweise nicht die beste langfristige Wahl, abgeleitete / Unterklassen auf diese Weise zu implementieren, sondern den Code zu verpacken und die vtable das tun zu lassen, was sie am besten kann. Wenn der neue Mann Fragen hat, können Sie ihn einfach auf die zeigen Dokumentation für was auch immer Lösung passt am besten .
Dies ist jedoch nur meine Meinung. : D.
quelle
Eine Möglichkeit, dies zu verstehen, besteht darin, mit SWIG eine Version davon zu erstellen. Sehen Sie, was es produziert und ob es übereinstimmt oder anders gemacht wird. Nach allem, was ich sagen kann, haben die Leute, die SWIG geschrieben haben, ein tiefes Verständnis für die Erweiterung von Python. Es kann jedenfalls nicht schaden zu sehen, wie sie Dinge tun. Es kann Ihnen helfen, dieses Problem zu verstehen.
quelle