3. Definition von Erweiterungstypen: Verschiedene Themen¶
Dieser Abschnitt gibt einen schnellen Überblick über die verschiedenen Typmethoden, die Sie implementieren können, und deren Funktionen.
Hier ist die Definition von PyTypeObject, wobei einige Felder, die nur in Debug-Builds verwendet werden, ausgelassen wurden
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
Py_ssize_t tp_vectorcall_offset;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
unsigned long tp_flags;
const char *tp_doc; /* Documentation string */
/* Assigned meaning in release 2.0 */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* Assigned meaning in release 2.1 */
/* rich comparisons */
richcmpfunc tp_richcompare;
/* weak reference enabler */
Py_ssize_t tp_weaklistoffset;
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* Attribute descriptor and subclassing stuff */
PyMethodDef *tp_methods;
PyMemberDef *tp_members;
PyGetSetDef *tp_getset;
// Strong reference on a heap type, borrowed reference on a static type
PyTypeObject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache; /* no longer used */
void *tp_subclasses; /* for static builtin types this is an index */
PyObject *tp_weaklist; /* not used for static builtin types */
destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6.
* If zero, the cache is invalid and must be initialized.
*/
unsigned int tp_version_tag;
destructor tp_finalize;
vectorcallfunc tp_vectorcall;
/* bitset of which type-watchers care about this type */
unsigned char tp_watched;
/* Number of tp_version_tag values used.
* Set to _Py_ATTR_CACHE_UNUSED if the attribute cache is
* disabled for this type (e.g. due to custom MRO entries).
* Otherwise, limited to MAX_VERSIONS_PER_CLASS (defined elsewhere).
*/
uint16_t tp_versions_used;
} PyTypeObject;
Das ist eine *Menge* von Methoden. Machen Sie sich keine zu großen Sorgen – wenn Sie einen Typ definieren möchten, ist die Wahrscheinlichkeit sehr hoch, dass Sie nur eine Handvoll davon implementieren werden.
Wie Sie wahrscheinlich inzwischen erwarten, werden wir dies durchgehen und weitere Informationen über die verschiedenen Handler geben. Wir werden nicht in der Reihenfolge vorgehen, in der sie in der Struktur definiert sind, da es viel historischen Ballast gibt, der die Reihenfolge der Felder beeinflusst. Es ist oft am einfachsten, ein Beispiel zu finden, das die benötigten Felder enthält, und dann die Werte für Ihren neuen Typ anzupassen.
const char *tp_name; /* For printing */
Der Name des Typs – wie im vorherigen Kapitel erwähnt, wird dieser an verschiedenen Stellen erscheinen, fast ausschließlich zu Diagnosezwecken. Versuchen Sie, etwas zu wählen, das in einer solchen Situation hilfreich ist!
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
Diese Felder teilen der Laufzeit mit, wie viel Speicher alloziert werden soll, wenn neue Objekte dieses Typs erstellt werden. Python hat einige integrierte Unterstützung für variable Längenstrukturen (denken Sie an: Zeichenketten, Tupel), woher das Feld tp_itemsize stammt. Dies wird später behandelt.
const char *tp_doc;
Hier können Sie einen String (oder seine Adresse) einfügen, der zurückgegeben werden soll, wenn das Python-Skript obj.__doc__ referenziert, um den Docstring abzurufen.
Nun kommen wir zu den grundlegenden Typmethoden – denjenigen, die die meisten Erweiterungstypen implementieren werden.
3.1. Finalisierung und Deallokation¶
destructor tp_dealloc;
Diese Funktion wird aufgerufen, wenn die Referenzanzahl der Instanz Ihres Typs auf null reduziert wird und der Python-Interpreter sie wieder freigeben möchte. Wenn Ihr Typ Speicher freigeben muss oder andere Bereinigungen durchführen muss, können Sie dies hier tun. Das Objekt selbst muss hier ebenfalls freigegeben werden. Hier ist ein Beispiel für diese Funktion
static void
newdatatype_dealloc(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
free(self->obj_UnderlyingDatatypePtr);
Py_TYPE(self)->tp_free(self);
}
Wenn Ihr Typ Garbage Collection unterstützt, sollte der Destruktor PyObject_GC_UnTrack() aufrufen, bevor er irgendwelche Mitgliedsfelder löscht.
static void
newdatatype_dealloc(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
PyObject_GC_UnTrack(op);
Py_CLEAR(self->other_obj);
...
Py_TYPE(self)->tp_free(self);
}
Eine wichtige Anforderung der Deallokatorfunktion ist, dass sie alle ausstehenden Ausnahmen in Ruhe lässt. Dies ist wichtig, da Deallokatoren häufig aufgerufen werden, wenn der Interpreter den Python-Stack entrollt; wenn der Stack aufgrund einer Ausnahme (und nicht aufgrund normaler Rückgaben) entrollt wird, geschieht nichts, um die Deallokatoren davor zu schützen, zu sehen, dass bereits eine Ausnahme gesetzt wurde. Alle Aktionen, die ein Deallokator durchführt und die zusätzlichen Python-Code ausführen könnten, können erkennen, dass bereits eine Ausnahme gesetzt wurde. Dies kann zu irreführenden Fehlern des Interpreters führen. Der richtige Weg, sich davor zu schützen, ist, eine ausstehende Ausnahme zu speichern, bevor die unsichere Aktion durchgeführt wird, und sie wiederherzustellen, wenn sie abgeschlossen ist. Dies kann mit den Funktionen PyErr_Fetch() und PyErr_Restore() erfolgen.
static void
my_dealloc(PyObject *obj)
{
MyObject *self = (MyObject *) obj;
PyObject *cbresult;
if (self->my_callback != NULL) {
PyObject *err_type, *err_value, *err_traceback;
/* This saves the current exception state */
PyErr_Fetch(&err_type, &err_value, &err_traceback);
cbresult = PyObject_CallNoArgs(self->my_callback);
if (cbresult == NULL) {
PyErr_WriteUnraisable(self->my_callback);
}
else {
Py_DECREF(cbresult);
}
/* This restores the saved exception state */
PyErr_Restore(err_type, err_value, err_traceback);
Py_DECREF(self->my_callback);
}
Py_TYPE(self)->tp_free(self);
}
Hinweis
Es gibt Einschränkungen, was Sie in einer Deallokatorfunktion sicher tun können. Erstens, wenn Ihr Typ Garbage Collection unterstützt (unter Verwendung von tp_traverse und/oder tp_clear), können einige der Mitgliedsfelder des Objekts bereits gelöscht oder finalisiert sein, wenn tp_dealloc aufgerufen wird. Zweitens, in tp_dealloc befindet sich Ihr Objekt in einem instabilen Zustand: seine Referenzanzahl ist null. Jeder Aufruf eines nicht-trivialen Objekts oder einer API (wie im obigen Beispiel) könnte dazu führen, dass tp_dealloc erneut aufgerufen wird, was zu einem doppelten Freigeben und einem Absturz führt.
Ab Python 3.4 wird empfohlen, keinen komplexen Finalisierungscode in tp_dealloc zu platzieren und stattdessen die neue Typmethode tp_finalize zu verwenden.
Siehe auch
PEP 442 erklärt das neue Finalisierungsschema.
3.2. Objektpräsentation¶
In Python gibt es zwei Möglichkeiten, eine textuelle Darstellung eines Objekts zu erzeugen: die Funktion repr() und die Funktion str(). (Die Funktion print() ruft einfach str() auf.) Diese Handler sind beide optional.
reprfunc tp_repr;
reprfunc tp_str;
Der tp_repr-Handler sollte ein String-Objekt mit einer Darstellung der Instanz zurückgeben, für die er aufgerufen wird. Hier ist ein einfaches Beispiel
static PyObject *
newdatatype_repr(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:%d}}",
self->obj_UnderlyingDatatypePtr->size);
}
Wenn kein tp_repr-Handler angegeben ist, stellt der Interpreter eine Darstellung bereit, die den tp_name des Typs und einen eindeutig identifizierenden Wert für das Objekt verwendet.
Der tp_str-Handler ist für str() dasselbe, was der oben beschriebene tp_repr-Handler für repr() ist; das heißt, er wird aufgerufen, wenn Python-Code str() für eine Instanz Ihres Objekts aufruft. Seine Implementierung ist der Funktion tp_repr sehr ähnlich, aber der resultierende String ist für den menschlichen Gebrauch bestimmt. Wenn tp_str nicht angegeben ist, wird stattdessen der tp_repr-Handler verwendet.
Hier ist ein einfaches Beispiel
static PyObject *
newdatatype_str(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
return PyUnicode_FromFormat("Stringified_newdatatype{{size:%d}}",
self->obj_UnderlyingDatatypePtr->size);
}
3.3. Attributmanagement¶
Für jedes Objekt, das Attribute unterstützen kann, muss der entsprechende Typ die Funktionen bereitstellen, die steuern, wie Attribute aufgelöst werden. Es muss eine Funktion geben, die Attribute abrufen kann (falls welche definiert sind), und eine weitere, um Attribute zu setzen (falls das Setzen von Attributen erlaubt ist). Das Entfernen eines Attributs ist ein Sonderfall, für den der an den Handler übergebene neue Wert NULL ist.
Python unterstützt zwei Paare von Attribut-Handlern; ein Typ, der Attribute unterstützt, muss nur die Funktionen für ein Paar implementieren. Der Unterschied besteht darin, dass ein Paar den Namen des Attributs als char* entgegennimmt, während das andere ein PyObject* akzeptiert. Jeder Typ kann das Paar verwenden, das für die Implementierung am sinnvollsten ist.
getattrfunc tp_getattr; /* char * version */
setattrfunc tp_setattr;
/* ... */
getattrofunc tp_getattro; /* PyObject * version */
setattrofunc tp_setattro;
Wenn der Zugriff auf Attribute eines Objekts immer eine einfache Operation ist (dies wird gleich erklärt), gibt es generische Implementierungen, die verwendet werden können, um die PyObject*-Version der Attributmanagementfunktionen bereitzustellen. Der tatsächliche Bedarf an typspezifischen Attribut-Handlern verschwand weitgehend ab Python 2.2, obwohl es viele Beispiele gibt, die noch nicht an die Verwendung einiger der neuen generischen Mechanismen angepasst wurden.
3.3.1. Generisches Attributmanagement¶
Die meisten Erweiterungstypen verwenden nur *einfache* Attribute. Was macht also Attribute einfach? Es gibt nur ein paar Bedingungen, die erfüllt sein müssen
Der Name der Attribute muss bekannt sein, wenn
PyType_Ready()aufgerufen wird.Es sind keine besonderen Verarbeitungen erforderlich, um zu erfassen, dass ein Attribut nachgeschlagen oder gesetzt wurde, noch müssen Aktionen basierend auf dem Wert durchgeführt werden.
Beachten Sie, dass diese Liste keine Einschränkungen für die Werte der Attribute, wann die Werte berechnet werden oder wie relevante Daten gespeichert werden, auferlegt.
Wenn PyType_Ready() aufgerufen wird, verwendet es drei Tabellen, auf die das Typobjekt verweist, um Deskriptoren zu erstellen, die im Dictionary des Typobjekts platziert werden. Jeder Deskriptor steuert den Zugriff auf ein Attribut des Instanzobjekts. Jede der Tabellen ist optional; wenn alle drei NULL sind, haben Instanzen des Typs nur Attribute, die von ihrem Basistyp geerbt wurden, und sollten die Felder tp_getattro und tp_setattro auf NULL setzen, damit der Basistyp Attribute verarbeiten kann.
Die Tabellen werden als drei Felder des Typobjekts deklariert
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
Wenn tp_methods nicht NULL ist, muss es auf ein Array von PyMethodDef-Strukturen verweisen. Jeder Eintrag in der Tabelle ist eine Instanz dieser Struktur
typedef struct PyMethodDef {
const char *ml_name; /* method name */
PyCFunction ml_meth; /* implementation function */
int ml_flags; /* flags */
const char *ml_doc; /* docstring */
} PyMethodDef;
Für jede von Typ bereitgestellte Methode sollte ein Eintrag definiert werden; für Methoden, die von einem Basistyp geerbt wurden, sind keine Einträge erforderlich. Ein zusätzlicher Eintrag ist am Ende erforderlich; er ist ein Sentinel, der das Ende des Arrays markiert. Das Feld ml_name des Sentinels muss NULL sein.
Die zweite Tabelle wird verwendet, um Attribute zu definieren, die direkt auf in der Instanz gespeicherte Daten abgebildet werden. Eine Vielzahl von primitiven C-Typen wird unterstützt, und der Zugriff kann schreibgeschützt oder les- und schreibbar sein. Die Strukturen in der Tabelle sind wie folgt definiert:
typedef struct PyMemberDef {
const char *name;
int type;
int offset;
int flags;
const char *doc;
} PyMemberDef;
Für jeden Eintrag in der Tabelle wird ein Deskriptor konstruiert und dem Typ hinzugefügt, der einen Wert aus der Instanzstruktur extrahieren kann. Das Feld type sollte einen Typencode wie Py_T_INT oder Py_T_DOUBLE enthalten; der Wert wird verwendet, um zu bestimmen, wie Python-Werte in und aus C-Werten konvertiert werden. Das Feld flags wird verwendet, um Flags zu speichern, die steuern, wie auf das Attribut zugegriffen werden kann: Sie können es auf Py_READONLY setzen, um Python-Code am Setzen zu hindern.
Ein interessanter Vorteil der Verwendung der tp_members-Tabelle zum Erstellen von Deskriptoren, die zur Laufzeit verwendet werden, ist, dass jedes auf diese Weise definierte Attribut eine zugehörige Dokumentationszeichenkette haben kann, indem einfach der Text in der Tabelle bereitgestellt wird. Eine Anwendung kann die Introspektions-API verwenden, um den Deskriptor vom Klassenobjekt abzurufen und die Dokumentationszeichenkette über dessen Attribut __doc__ abzurufen.
Wie bei der tp_methods-Tabelle ist ein Sentinel-Eintrag mit einem ml_name-Wert von NULL erforderlich.
3.3.2. Typspezifisches Attributmanagement¶
Zur Vereinfachung wird hier nur die char*-Version demonstriert; der Typ des Namensparameters ist der einzige Unterschied zwischen den char*- und PyObject*-Varianten der Schnittstelle. Dieses Beispiel tut effektiv dasselbe wie das generische Beispiel oben, verwendet aber nicht die in Python 2.2 hinzugefügte generische Unterstützung. Es erklärt, wie die Handlerfunktionen aufgerufen werden, sodass Sie verstehen, was zu tun ist, wenn Sie ihre Funktionalität erweitern müssen.
Der tp_getattr-Handler wird aufgerufen, wenn das Objekt eine Attributabfrage erfordert. Er wird in denselben Situationen aufgerufen, in denen die Methode __getattr__() einer Klasse aufgerufen würde.
Hier ist ein Beispiel
static PyObject *
newdatatype_getattr(PyObject *op, char *name)
{
newdatatypeobject *self = (newdatatypeobject *) op;
if (strcmp(name, "data") == 0) {
return PyLong_FromLong(self->data);
}
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%.400s'",
Py_TYPE(self)->tp_name, name);
return NULL;
}
Der tp_setattr-Handler wird aufgerufen, wenn die Methode __setattr__() oder __delattr__() einer Klasseninstanz aufgerufen würde. Wenn ein Attribut gelöscht werden soll, ist der dritte Parameter NULL. Hier ist ein Beispiel, das einfach eine Ausnahme auslöst; wenn das alles wäre, was Sie wollten, sollte der tp_setattr-Handler auf NULL gesetzt werden.
static int
newdatatype_setattr(PyObject *op, char *name, PyObject *v)
{
PyErr_Format(PyExc_RuntimeError, "Read-only attribute: %s", name);
return -1;
}
3.4. Objektvergleich¶
richcmpfunc tp_richcompare;
Der tp_richcompare-Handler wird aufgerufen, wenn Vergleiche benötigt werden. Er ist analog zu den Rich Comparison-Methoden wie __lt__() und wird auch von PyObject_RichCompare() und PyObject_RichCompareBool() aufgerufen.
Diese Funktion wird mit zwei Python-Objekten und dem Operator als Argumente aufgerufen, wobei der Operator einer von Py_EQ, Py_NE, Py_LE, Py_GE, Py_LT oder Py_GT ist. Sie sollte die beiden Objekte im Hinblick auf den angegebenen Operator vergleichen und Py_True oder Py_False zurückgeben, wenn der Vergleich erfolgreich ist, Py_NotImplemented, um anzuzeigen, dass der Vergleich nicht implementiert ist und die Vergleichsmethode des anderen Objekts versucht werden sollte, oder NULL, wenn eine Ausnahme gesetzt wurde.
Hier ist eine Beispielimplementierung für einen Datentyp, der als gleich gilt, wenn die Größe eines internen Zeigers gleich ist
static PyObject *
newdatatype_richcmp(PyObject *lhs, PyObject *rhs, int op)
{
newdatatypeobject *obj1 = (newdatatypeobject *) lhs;
newdatatypeobject *obj2 = (newdatatypeobject *) rhs;
PyObject *result;
int c, size1, size2;
/* code to make sure that both arguments are of type
newdatatype omitted */
size1 = obj1->obj_UnderlyingDatatypePtr->size;
size2 = obj2->obj_UnderlyingDatatypePtr->size;
switch (op) {
case Py_LT: c = size1 < size2; break;
case Py_LE: c = size1 <= size2; break;
case Py_EQ: c = size1 == size2; break;
case Py_NE: c = size1 != size2; break;
case Py_GT: c = size1 > size2; break;
case Py_GE: c = size1 >= size2; break;
}
result = c ? Py_True : Py_False;
return Py_NewRef(result);
}
3.5. Unterstützung für abstrakte Protokolle¶
Python unterstützt eine Vielzahl von *abstrakten* „Protokollen“; die spezifischen Schnittstellen für die Verwendung dieser Schnittstellen sind in Abstract Objects Layer dokumentiert.
Einige dieser abstrakten Protokolle wurden früh in der Entwicklung der Python-Implementierung definiert. Insbesondere die Protokolle für Zahlen, Mappings und Sequenzen sind seit Beginn von Python vorhanden. Andere Protokolle wurden im Laufe der Zeit hinzugefügt. Für Protokolle, die von mehreren Handler-Routinen der Typimplementierung abhängen, wurden die älteren Protokolle als optionale Blöcke von Handlern definiert, auf die das Typobjekt verweist. Für neuere Protokolle gibt es zusätzliche Slots im Haupttypobjekt, wobei ein Flag-Bit gesetzt wird, um anzuzeigen, dass die Slots vorhanden sind und vom Interpreter überprüft werden sollten. (Das Flag-Bit zeigt nicht an, dass die Slot-Werte nicht NULL sind. Das Flag kann gesetzt werden, um die Anwesenheit eines Slots anzuzeigen, aber ein Slot kann immer noch nicht ausgefüllt sein.)
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
Wenn Ihr Objekt wie ein Zahlen-, Sequenz- oder Mapping-Objekt agieren soll, platzieren Sie die Adresse einer Struktur, die den C-Typ PyNumberMethods, PySequenceMethods oder PyMappingMethods implementiert, entsprechend. Es liegt an Ihnen, diese Struktur mit den entsprechenden Werten zu füllen. Beispiele für die Verwendung jedes dieser finden Sie im Verzeichnis Objects der Python-Quellcodeverteilung.
hashfunc tp_hash;
Diese Funktion sollte, wenn Sie sie bereitstellen möchten, eine Hash-Nummer für eine Instanz Ihres Datentyps zurückgeben. Hier ist ein einfaches Beispiel
static Py_hash_t
newdatatype_hash(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
Py_hash_t result;
result = self->some_size + 32767 * self->some_number;
if (result == -1) {
result = -2;
}
return result;
}
Py_hash_t ist ein vorzeichenbehafteter Ganzzahltyp mit einer plattformabhängigen Breite. Die Rückgabe von -1 von tp_hash zeigt einen Fehler an, weshalb Sie vorsichtig sein sollten, ihn nicht zurückzugeben, wenn die Hash-Berechnung erfolgreich ist, wie oben gezeigt.
ternaryfunc tp_call;
Diese Funktion wird aufgerufen, wenn eine Instanz Ihres Datentyps "aufgerufen" wird, z. B. wenn obj1 eine Instanz Ihres Datentyps ist und das Python-Skript obj1('hallo') enthält, wird der tp_call-Handler aufgerufen.
Diese Funktion nimmt drei Argumente entgegen
self ist die Instanz des Datentyps, die Gegenstand des Aufrufs ist. Wenn der Aufruf
obj1('hallo')lautet, dann ist selfobj1.args ist ein Tupel, das die Argumente für den Aufruf enthält. Sie können
PyArg_ParseTuple()verwenden, um die Argumente zu extrahieren.kwds ist ein Dictionary von Schlüsselwortargumenten, die übergeben wurden. Wenn dieses nicht
NULList und Sie Schlüsselwortargumente unterstützen, verwenden SiePyArg_ParseTupleAndKeywords(), um die Argumente zu extrahieren. Wenn Sie keine Schlüsselwortargumente unterstützen und dieses nichtNULList, lösen Sie einenTypeErrormit einer Meldung aus, dass Schlüsselwortargumente nicht unterstützt werden.
Hier ist eine Spielzeug-tp_call-Implementierung
static PyObject *
newdatatype_call(PyObject *op, PyObject *args, PyObject *kwds)
{
newdatatypeobject *self = (newdatatypeobject *) op;
PyObject *result;
const char *arg1;
const char *arg2;
const char *arg3;
if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
return NULL;
}
result = PyUnicode_FromFormat(
"Returning -- value: [%d] arg1: [%s] arg2: [%s] arg3: [%s]\n",
self->obj_UnderlyingDatatypePtr->size,
arg1, arg2, arg3);
return result;
}
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
Diese Funktionen unterstützen das Iterator-Protokoll. Beide Handler nehmen genau einen Parameter entgegen, die Instanz, für die sie aufgerufen werden, und geben eine neue Referenz zurück. Im Fehlerfall sollten sie eine Ausnahme setzen und NULL zurückgeben. tp_iter entspricht der Python-Methode __iter__(), während tp_iternext der Python-Methode __next__() entspricht.
Jedes iterierbare Objekt muss den tp_iter-Handler implementieren, der ein Iterator-Objekt zurückgeben muss. Hier gelten dieselben Richtlinien wie für Python-Klassen
Für Sammlungen (wie Listen und Tupel), die mehrere unabhängige Iteratoren unterstützen können, muss bei jedem Aufruf von
tp_iterein neuer Iterator erstellt und zurückgegeben werden.Objekte, die nur einmal iteriert werden können (normalerweise aufgrund von Seiteneffekten der Iteration, wie z. B. Dateiobjekte), können
tp_iterimplementieren, indem sie eine neue Referenz auf sich selbst zurückgeben – und sollten daher auch dentp_iternext-Handler implementieren.
Jedes Iterator-Objekt sollte sowohl tp_iter als auch tp_iternext implementieren. Der tp_iter-Handler eines Iterators sollte eine neue Referenz auf den Iterator zurückgeben. Sein tp_iternext-Handler sollte eine neue Referenz auf das nächste Objekt in der Iteration zurückgeben, falls vorhanden. Wenn die Iteration das Ende erreicht hat, kann tp_iternext NULL zurückgeben, ohne eine Ausnahme zu setzen, oder es kann StopIteration *zusätzlich* zur Rückgabe von NULL setzen; das Vermeiden der Ausnahme kann eine etwas bessere Leistung erzielen. Wenn ein tatsächlicher Fehler auftritt, sollte tp_iternext immer eine Ausnahme setzen und NULL zurückgeben.
3.6. Unterstützung für schwache Referenzen¶
Eines der Ziele der schwachen Referenzimplementierung von Python ist es, jedem Typ die Teilnahme am schwachen Referenzmechanismus zu ermöglichen, ohne die Leistung kritischer Objekte (wie Zahlen) zu beeinträchtigen.
Siehe auch
Dokumentation für das Modul weakref.
Damit ein Objekt schwach referenzierbar ist, muss der Erweiterungstyp das Bit Py_TPFLAGS_MANAGED_WEAKREF des Feldes tp_flags setzen. Das ältere Feld tp_weaklistoffset sollte auf null belassen werden.
Konkret würde das statisch deklarierte Typobjekt so aussehen
static PyTypeObject TrivialType = {
PyVarObject_HEAD_INIT(NULL, 0)
/* ... other members omitted for brevity ... */
.tp_flags = Py_TPFLAGS_MANAGED_WEAKREF | ...,
};
Die einzige weitere Ergänzung ist, dass tp_dealloc alle schwachen Referenzen löschen muss (durch Aufrufen von PyObject_ClearWeakRefs())
static void
Trivial_dealloc(PyObject *op)
{
/* Clear weakrefs first before calling any destructors */
PyObject_ClearWeakRefs(op);
/* ... remainder of destruction code omitted for brevity ... */
Py_TYPE(op)->tp_free(op);
}
3.7. Weitere Vorschläge¶
Um zu lernen, wie Sie eine bestimmte Methode für Ihren neuen Datentyp implementieren, holen Sie sich den CPython-Quellcode. Gehen Sie in das Verzeichnis Objects und suchen Sie in den C-Quelldateien nach tp_ gefolgt von der Funktion, die Sie möchten (z. B. tp_richcompare). Sie werden Beispiele für die Funktion finden, die Sie implementieren möchten.
Wenn Sie überprüfen müssen, ob ein Objekt eine konkrete Instanz des Typs ist, den Sie implementieren, verwenden Sie die Funktion PyObject_TypeCheck(). Ein Beispiel für seine Verwendung könnte wie folgt aussehen
if (!PyObject_TypeCheck(some_object, &MyType)) {
PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
return NULL;
}
Siehe auch
- Laden Sie CPython-Quellcode-Releases herunter.
- Das CPython-Projekt auf GitHub, wo der CPython-Quellcode entwickelt wird.