Isolating Extension Modules¶
Wer sollte das lesen¶
Diese Anleitung richtet sich an Wartende von C-API-Erweiterungen, die diese Erweiterung sicherer in Anwendungen verwenden möchten, in denen Python selbst als Bibliothek eingesetzt wird.
Hintergrund¶
Ein *Interpreter* ist der Kontext, in dem Python-Code ausgeführt wird. Er enthält Konfiguration (z. B. den Importpfad) und Laufzeitzustand (z. B. die Menge der importierten Module).
Python unterstützt die Ausführung mehrerer Interpreter in einem Prozess. Es gibt zwei Fälle zu bedenken – Benutzer können Interpreter ausführen
sequenziell, mit mehreren
Py_InitializeEx()/Py_FinalizeEx()-Zyklen undparallel, indem sie „Sub-Interpreter“ mit
Py_NewInterpreter()/Py_EndInterpreter()verwalten.
Beide Fälle (und Kombinationen davon) wären am nützlichsten, wenn Python in eine Bibliothek eingebettet wird. Bibliotheken sollten im Allgemeinen keine Annahmen über die sie nutzende Anwendung treffen, was einschließt, von einem prozessweiten „Haupt-Python-Interpreter“ auszugehen.
Historisch gesehen gehen Python-Erweiterungsmodule mit diesem Anwendungsfall nicht gut um. Viele Erweiterungsmodule (und sogar einige Standardbibliotheksmodule) verwenden *prozessweiten* globalen Zustand, da C static Variablen extrem einfach zu verwenden sind. Dadurch landen Daten, die für einen Interpreter spezifisch sein sollten, bei anderen Interpretern. Sofern der Erweiterungsentwickler nicht vorsichtig ist, ist es sehr einfach, Randfälle zu erzeugen, die zu Abstürzen führen, wenn ein Modul in mehr als einem Interpreter im selben Prozess geladen wird.
Leider ist *pro-Interpreter*-Zustand nicht einfach zu erreichen. Erweiterungsautoren neigen dazu, bei der Entwicklung nicht an mehrere Interpreter zu denken, und es ist derzeit umständlich, das Verhalten zu testen.
Modulzustaand pro Modul einführen¶
Anstatt sich auf Modulzustaand pro Interpreter zu konzentrieren, entwickelt sich die C-API von Python weiter, um den feingranulareren *Modulzustaand pro Modul* besser zu unterstützen. Das bedeutet, dass C-Level-Daten an ein *Modulobjekt* angehängt werden sollten. Jeder Interpreter erstellt sein eigenes Modulobjekt und hält die Daten getrennt. Zum Testen der Isolation können sogar mehrere Modulobjekte, die zu einer einzelnen Erweiterung gehören, in einem einzigen Interpreter geladen werden.
Modulzustaand pro Modul bietet eine einfache Möglichkeit, über Lebensdauer und Ressourcenverantwortung nachzudenken: Die Erweiterungsmodul wird initialisiert, wenn ein Modulobjekt erstellt wird, und räumt auf, wenn es freigegeben wird. In dieser Hinsicht ist ein Modul wie jedes andere PyObject*; es gibt keine „Beim Interpreter-Abschluss“-Hooks, über die man nachdenken – oder vergessen – muss.
Beachten Sie, dass es Anwendungsfälle für verschiedene Arten von „Globalen“ gibt: pro Prozess, pro Interpreter, pro Thread oder pro Task. Mit Modulzustaand pro Modul als Standard sind diese immer noch möglich, aber Sie sollten sie als Ausnahmefälle behandeln: Wenn Sie sie benötigen, sollten Sie ihnen zusätzliche Sorgfalt und Tests widmen. (Beachten Sie, dass diese Anleitung sie nicht abdeckt.)
Isolierte Modulobjekte¶
Der wichtigste Punkt, den Sie bei der Entwicklung eines Erweiterungsmoduls beachten sollten, ist, dass mehrere Modulobjekte aus einer einzigen gemeinsam genutzten Bibliothek erstellt werden können. Zum Beispiel
>>> import sys
>>> import binascii
>>> old_binascii = binascii
>>> del sys.modules['binascii']
>>> import binascii # create a new module object
>>> old_binascii == binascii
False
Als Faustregel sollten die beiden Module vollständig unabhängig sein. Alle Objekte und Zustände, die spezifisch für das Modul sind, sollten in das Modulobjekt eingekapselt, nicht mit anderen Modulobjekten geteilt und beim Freigeben des Modulobjekts bereinigt werden. Da dies nur eine Faustregel ist, sind Ausnahmen möglich (siehe Globalen Zustand verwalten), aber sie erfordern mehr Überlegung und Aufmerksamkeit für Randfälle.
Während einige Module weniger strenge Einschränkungen verkraften könnten, erleichtern isolierte Module das Festlegen klarer Erwartungen und Richtlinien, die in einer Vielzahl von Anwendungsfällen funktionieren.
Überraschende Randfälle¶
Beachten Sie, dass isolierte Module einige überraschende Randfälle erzeugen. Am bemerkenswertesten ist, dass jedes Modulobjekt in der Regel seine Klassen und Ausnahmen nicht mit anderen ähnlichen Modulen teilt. Fortfahrend mit dem Beispiel oben, beachten Sie, dass old_binascii.Error und binascii.Error separate Objekte sind. Im folgenden Code wird die Ausnahme *nicht* abgefangen
>>> old_binascii.Error == binascii.Error
False
>>> try:
... old_binascii.unhexlify(b'qwertyuiop')
... except binascii.Error:
... print('boo')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
binascii.Error: Non-hexadecimal digit found
Dies ist erwartet. Beachten Sie, dass reine Python-Module sich gleich verhalten: Das ist Teil der Funktionsweise von Python.
Das Ziel ist es, Erweiterungsmodule auf C-Ebene sicher zu machen, nicht Hacks intuitiv verhalten zu lassen. Das „manuelle“ Mutieren von sys.modules gilt als Hack.
Module mit mehreren Interpretern sicher machen¶
Globalen Zustand verwalten¶
Manchmal ist der Zustand, der mit einem Python-Modul verbunden ist, nicht spezifisch für dieses Modul, sondern für den gesamten Prozess (oder etwas „globaleres“ als ein Modul). Zum Beispiel
Das Modul
readlineverwaltet *das* Terminal.Ein Modul, das auf einer Platine läuft, möchte *die* On-Board-LED steuern.
In diesen Fällen sollte das Python-Modul *Zugriff* auf den globalen Zustand gewähren, anstatt ihn zu *besitzen*. Wenn möglich, schreiben Sie das Modul so, dass mehrere Kopien davon unabhängig auf den Zustand zugreifen können (zusammen mit anderen Bibliotheken, sei es für Python oder andere Sprachen). Wenn das nicht möglich ist, erwägen Sie explizites Sperren.
Wenn es notwendig ist, prozessweiten globalen Zustand zu verwenden, ist der einfachste Weg, Probleme mit mehreren Interpretern zu vermeiden, explizit zu verhindern, dass ein Modul mehr als einmal pro Prozess geladen wird – siehe Opt-Out: Nur ein Modulobjekt pro Prozess zulassen.
Modulzustaand pro Modul verwalten¶
Um Modulzustaand pro Modul zu verwenden, verwenden Sie Multi-Phase-Initialisierung von Erweiterungsmodulen. Dies signalisiert, dass Ihr Modul mehrere Interpreter korrekt unterstützt.
Setzen Sie PyModuleDef.m_size auf eine positive Zahl, um so viele Bytes an speicher lokal für das Modul anzufordern. Normalerweise wird dies auf die Größe einer modulspezifischen struct gesetzt, die den gesamten C-Level-Zustand des Moduls speichern kann. Insbesondere sollten Sie hier Zeiger auf Klassen (einschließlich Ausnahmen, aber ausschließlich statische Typen) und Einstellungen (z. B. csvs field_size_limit) ablegen, die der C-Code zur Funktionsweise benötigt.
Hinweis
Eine andere Option ist, den Zustand im __dict__ des Moduls zu speichern, aber Sie müssen Abstürze vermeiden, wenn Benutzer __dict__ aus Python-Code ändern. Dies bedeutet normalerweise Fehler- und Typprüfung auf C-Ebene, was leicht falsch gemacht und schwer ausreichend getestet werden kann.
Wenn jedoch Modulzustaand nicht im C-Code benötigt wird, ist es eine gute Idee, ihn nur im __dict__ zu speichern.
Wenn der Modulzustaand PyObject-Zeiger enthält, muss das Modulobjekt Referenzen auf diese Objekte halten und die Modul-Hooks m_traverse, m_clear und m_free implementieren. Diese funktionieren wie tp_traverse, tp_clear und tp_free einer Klasse. Das Hinzufügen wird etwas Arbeit kosten und den Code länger machen; dies ist der Preis für Module, die sauber entladen werden können.
Ein Beispiel für ein Modul mit Modulzustaand pro Modul ist derzeit als xxlimited verfügbar; Beispiel für die Modulinitialisierung am Ende der Datei.
Opt-Out: Nur ein Modulobjekt pro Prozess zulassen¶
Eine nicht-negative Zahl für PyModuleDef.m_size signalisiert, dass ein Modul mehrere Interpreter korrekt unterstützt. Wenn dies für Ihr Modul noch nicht der Fall ist, können Sie Ihr Modul explizit nur einmal pro Prozess laden lassen. Zum Beispiel
// A process-wide flag
static int loaded = 0;
// Mutex to provide thread safety (only needed for free-threaded Python)
static PyMutex modinit_mutex = {0};
static int
exec_module(PyObject* module)
{
PyMutex_Lock(&modinit_mutex);
if (loaded) {
PyMutex_Unlock(&modinit_mutex);
PyErr_SetString(PyExc_ImportError,
"cannot load module more than once per process");
return -1;
}
loaded = 1;
PyMutex_Unlock(&modinit_mutex);
// ... rest of initialization
}
Wenn die Funktion PyModuleDef.m_clear Ihres Moduls auf zukünftige Reinitialisierung vorbereitet werden kann, sollte sie das Flag loaded löschen. In diesem Fall unterstützt Ihr Modul keine *gleichzeitige* Existenz mehrerer Instanzen, aber es unterstützt beispielsweise das Laden nach dem Python-Laufzeitende (Py_FinalizeEx()) und die Reinitialisierung (Py_Initialize()).
Zugriff auf Modulzustaand aus Funktionen¶
Der Zugriff auf den Zustand aus Modul-Level-Funktionen ist einfach. Funktionen erhalten das Modulobjekt als erstes Argument; zum Extrahieren des Zustands können Sie PyModule_GetState verwenden
static PyObject *
func(PyObject *module, PyObject *args)
{
my_struct *state = (my_struct*)PyModule_GetState(module);
if (state == NULL) {
return NULL;
}
// ... rest of logic
}
Hinweis
PyModule_GetState kann NULL zurückgeben, ohne eine Ausnahme zu setzen, wenn kein Modulzustaand vorhanden ist, d. h. PyModuleDef.m_size war null. In Ihrem eigenen Modul haben Sie die Kontrolle über m_size, daher ist dies leicht zu verhindern.
Heap-Typen¶
Traditionell sind Typen, die in C-Code definiert sind, *statisch*; das heißt, static PyTypeObject Strukturen, die direkt im Code definiert und mit PyType_Ready() initialisiert werden.
Solche Typen sind notwendigerweise prozessweit gemeinsam genutzt. Die gemeinsame Nutzung zwischen Modulobjekten erfordert Aufmerksamkeit für jeden Zustand, den sie besitzen oder auf den sie zugreifen. Um mögliche Probleme zu begrenzen, sind statische Typen auf Python-Ebene unveränderlich: Zum Beispiel können Sie nicht str.myattribute = 123 setzen.
CPython-Implementierungsdetail: Die gemeinsame Nutzung wirklich unveränderlicher Objekte zwischen Interpretern ist in Ordnung, solange sie keinen Zugriff auf veränderliche Objekte bieten. In CPython hat jedes Python-Objekt jedoch ein veränderliches Implementierungsdetail: die Referenzzählung. Änderungen an der Referenzzählung werden durch die GIL geschützt. Daher ist Code, der Python-Objekte über Interpreter hinweg teilt, implizit von der aktuellen, prozessweiten GIL von CPython abhängig.
Da sie unveränderlich und prozessweit global sind, können statische Typen nicht auf „ihren“ Modulzustaand zugreifen. Wenn eine Methode eines solchen Typs Zugriff auf Modulzustaand benötigt, muss der Typ in einen *heap-allokierten Typ* oder kurz *Heap-Typ* konvertiert werden. Diese entsprechen eher Klassen, die von Pythons class-Anweisung erstellt werden.
Für neue Module ist die Verwendung von Heap-Typen standardmäßig eine gute Faustregel.
Statische Typen in Heap-Typen ändern¶
Statische Typen können in Heap-Typen konvertiert werden, aber beachten Sie, dass die Heap-Typ-API nicht für eine „verlustfreie“ Konvertierung von statischen Typen ausgelegt ist – das heißt, das Erstellen eines Typs, der sich exakt wie ein gegebener statischer Typ verhält. Beim Umschreiben der Klassendefinition in einer neuen API werden Sie daher wahrscheinlich einige Details unabsichtlich ändern (z. B. Pickle-Fähigkeit oder geerbte Slots). Testen Sie immer die Details, die für Sie wichtig sind.
Achten Sie insbesondere auf die folgenden beiden Punkte (aber beachten Sie, dass dies keine umfassende Liste ist)
Im Gegensatz zu statischen Typen sind Heap-Typobjekte standardmäßig veränderlich. Verwenden Sie das Flag
Py_TPFLAGS_IMMUTABLETYPE, um Veränderlichkeit zu verhindern.Heap-Typen erben standardmäßig
tp_new, sodass es möglich werden kann, sie aus Python-Code zu instanziieren. Dies können Sie mit dem FlagPy_TPFLAGS_DISALLOW_INSTANTIATIONverhindern.
Heap-Typen definieren¶
Heap-Typen können erstellt werden, indem eine PyType_Spec-Struktur, eine Beschreibung oder „Blaupause“ einer Klasse, gefüllt und PyType_FromModuleAndSpec() aufgerufen wird, um ein neues Klassenobjekt zu konstruieren.
Hinweis
Andere Funktionen, wie PyType_FromSpec(), können ebenfalls Heap-Typen erstellen, aber PyType_FromModuleAndSpec() verknüpft das Modul mit der Klasse, was den Zugriff auf den Modulzustaand aus Methoden ermöglicht.
Die Klasse sollte generell *sowohl* im Modulzustaand (für sicheren Zugriff aus C) *als auch* im __dict__ des Moduls (für Zugriff aus Python-Code) gespeichert werden.
Garbage-Collection-Protokoll¶
Instanzen von Heap-Typen halten eine Referenz auf ihren Typ. Dies stellt sicher, dass der Typ nicht zerstört wird, bevor alle seine Instanzen vernichtet sind, kann aber zu Referenzzyklen führen, die vom Garbage Collector durchbrochen werden müssen.
Um Speicherlecks zu vermeiden, müssen Instanzen von Heap-Typen das Garbage-Collection-Protokoll implementieren. Das heißt, Heap-Typen sollten
Das Flag
Py_TPFLAGS_HAVE_GChaben.Eine Traverse-Funktion mit
Py_tp_traversedefinieren, die den Typ besucht (z. B. mitPy_VISIT(Py_TYPE(self))).
Weitere Überlegungen finden Sie in der Dokumentation zu Py_TPFLAGS_HAVE_GC und tp_traverse.
Die API zur Definition von Heap-Typen ist organisch gewachsen, was sie in ihrem aktuellen Zustand etwas umständlich zu verwenden macht. Die folgenden Abschnitte führen Sie durch gängige Probleme.
tp_traverse in Python 3.8 und niedriger¶
Die Anforderung, den Typ von tp_traverse zu besuchen, wurde in Python 3.9 hinzugefügt. Wenn Sie Python 3.8 und niedriger unterstützen, darf die Traverse-Funktion den Typ *nicht* besuchen, daher muss sie komplizierter sein
static int my_traverse(PyObject *self, visitproc visit, void *arg)
{
if (Py_Version >= 0x03090000) {
Py_VISIT(Py_TYPE(self));
}
return 0;
}
Leider wurde Py_Version erst in Python 3.11 hinzugefügt. Als Ersatz verwenden Sie
PY_VERSION_HEX, wenn nicht die stabile ABI verwendet wird, odersys.version_info(überPySys_GetObject()undPyArg_ParseTuple()).
Delegieren von tp_traverse¶
Wenn Ihre Traverse-Funktion an die tp_traverse ihrer Basisklasse (oder eines anderen Typs) delegiert, stellen Sie sicher, dass Py_TYPE(self) nur einmal besucht wird. Beachten Sie, dass nur Heap-Typen erwartet werden, den Typ in tp_traverse zu besuchen.
Zum Beispiel, wenn Ihre Traverse-Funktion
base->tp_traverse(self, visit, arg)
…und base eine statische Klasse sein könnte, dann sollte sie auch
if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
// a heap type's tp_traverse already visited Py_TYPE(self)
} else {
if (Py_Version >= 0x03090000) {
Py_VISIT(Py_TYPE(self));
}
}
Es ist nicht notwendig, die Referenzzählung des Typs in tp_new und tp_clear zu behandeln.
tp_dealloc definieren¶
Wenn Ihr Typ eine benutzerdefinierte tp_dealloc-Funktion hat, muss sie
vor der Invalidierung von Feldern
PyObject_GC_UnTrack()aufrufen unddie Referenzzählung des Typs dekrementieren.
Um den Typ gültig zu halten, während tp_free aufgerufen wird, muss die Referenzzählung des Typs *nach* der Deallokation der Instanz dekrementiert werden. Zum Beispiel
static void my_dealloc(PyObject *self)
{
PyObject_GC_UnTrack(self);
...
PyTypeObject *type = Py_TYPE(self);
type->tp_free(self);
Py_DECREF(type);
}
Die Standard- tp_dealloc-Funktion macht dies, daher müssen Sie sie nicht hinzufügen, wenn Ihr Typ tp_dealloc *nicht* überschreibt.
tp_free nicht überschreiben¶
Der tp_free-Slot eines Heap-Typs muss auf PyObject_GC_Del() gesetzt werden. Dies ist der Standard; überschreiben Sie ihn nicht.
Vermeiden von PyObject_New¶
GC-verfolgte Objekte müssen mit GC-freundlichen Funktionen alloziert werden.
Wenn Sie PyObject_New() oder PyObject_NewVar() verwenden
Rufen Sie den
tp_alloc-Slot des Typs ab und rufen Sie ihn auf, wenn möglich. Das heißt, ersetzen SieTYPE *o = PyObject_New(TYPE, typeobj)durchTYPE *o = typeobj->tp_alloc(typeobj, 0);
Ersetzen Sie
o = PyObject_NewVar(TYPE, typeobj, size)durch dasselbe, aber verwenden Sie size anstelle von 0.Wenn das Obige nicht möglich ist (z. B. innerhalb eines benutzerdefinierten
tp_alloc), rufen SiePyObject_GC_New()oderPyObject_GC_NewVar()aufTYPE *o = PyObject_GC_New(TYPE, typeobj); TYPE *o = PyObject_GC_NewVar(TYPE, typeobj, size);
Zugriff auf Modulzustaand aus Klassen¶
Wenn Sie ein Typobjekt haben, das mit PyType_FromModuleAndSpec() definiert wurde, können Sie PyType_GetModule() aufrufen, um das zugehörige Modul zu erhalten, und dann PyModule_GetState() aufrufen, um den Modulzustaand zu erhalten.
Um etwas mühsamen Boilerplate-Code für die Fehlerbehandlung zu sparen, können Sie diese beiden Schritte mit PyType_GetModuleState() kombinieren, was zu folgendem Ergebnis führt:
my_struct *state = (my_struct*)PyType_GetModuleState(type);
if (state == NULL) {
return NULL;
}
Zugriff auf Modulzustaand aus regulären Methoden¶
Der Zugriff auf den Modul-Level-Zustand aus Methoden einer Klasse ist etwas komplizierter, aber dank der in Python 3.9 eingeführten API möglich. Um den Zustand zu erhalten, müssen Sie zuerst die *definierende Klasse* erhalten und dann von ihr den Modulzustaand abrufen.
Die größte Hürde ist das Ermitteln der *Klasse, in der eine Methode definiert wurde*, oder kurz der „definierenden Klasse“ dieser Methode. Die definierende Klasse kann eine Referenz auf das Modul haben, zu dem sie gehört.
Verwechseln Sie die definierende Klasse nicht mit Py_TYPE(self). Wenn die Methode für eine *Unterklasse* Ihres Typs aufgerufen wird, bezieht sich Py_TYPE(self) auf diese Unterklasse, die in einem anderen Modul als Ihrem definiert sein kann.
Hinweis
Der folgende Python-Code kann das Konzept veranschaulichen. Base.get_defining_class gibt Base zurück, auch wenn type(self) == Sub
class Base:
def get_type_of_self(self):
return type(self)
def get_defining_class(self):
return __class__
class Sub(Base):
pass
Damit eine Methode ihre „definierende Klasse“ erhält, muss sie die Aufrufkonvention | METH_METHOD | METH_FASTCALL | METH_KEYWORDS und die entsprechende PyCMethod-Signatur verwenden
PyObject *PyCMethod(
PyObject *self, // object the method was called on
PyTypeObject *defining_class, // defining class
PyObject *const *args, // C array of arguments
Py_ssize_t nargs, // length of "args"
PyObject *kwnames) // NULL, or dict of keyword arguments
Sobald Sie die definierende Klasse haben, rufen Sie PyType_GetModuleState() auf, um den Zustand ihres zugehörigen Moduls zu erhalten.
Zum Beispiel
static PyObject *
example_method(PyObject *self,
PyTypeObject *defining_class,
PyObject *const *args,
Py_ssize_t nargs,
PyObject *kwnames)
{
my_struct *state = (my_struct*)PyType_GetModuleState(defining_class);
if (state == NULL) {
return NULL;
}
... // rest of logic
}
PyDoc_STRVAR(example_method_doc, "...");
static PyMethodDef my_methods[] = {
{"example_method",
(PyCFunction)(void(*)(void))example_method,
METH_METHOD|METH_FASTCALL|METH_KEYWORDS,
example_method_doc}
{NULL},
}
Zugriff auf Modulzustaand aus Slot-Methoden, Gettern und Settern¶
Hinweis
Dies ist neu in Python 3.11.
Slot-Methoden – die schnellen C-Äquivalente für spezielle Methoden, wie z. B. nb_add für __add__ oder tp_new für die Initialisierung – haben eine sehr einfache API, die es nicht erlaubt, die definierende Klasse zu übergeben, wie es bei PyCMethod der Fall ist. Das Gleiche gilt für Getter und Setter, die mit PyGetSetDef definiert werden.
Um auf den Modulzustaand in diesen Fällen zuzugreifen, verwenden Sie die Funktion PyType_GetModuleByDef() und übergeben Sie die Moduldefinition. Sobald Sie das Modul haben, rufen Sie PyModule_GetState() auf, um den Zustand zu erhalten
PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &module_def);
my_struct *state = (my_struct*)PyModule_GetState(module);
if (state == NULL) {
return NULL;
}
PyType_GetModuleByDef() funktioniert, indem es die Method Resolution Order (d. h. alle Oberklassen) durchsucht, um die erste Oberklasse zu finden, die ein entsprechendes Modul hat.
Hinweis
In sehr exotischen Fällen (Vererbungsketten, die mehrere Module umfassen, die aus derselben Definition erstellt wurden) gibt PyType_GetModuleByDef() möglicherweise nicht das Modul der tatsächlichen definierenden Klasse zurück. Es wird jedoch immer ein Modul mit derselben Definition zurückgeben, was eine kompatible C-Speicherlayout-Struktur gewährleistet.
Lebensdauer des Modulzustaands¶
Wenn ein Modulobjekt garbage collected wird, wird sein Modulzustaand freigegeben. Für jeden Zeiger auf (einen Teil des) Modulzustaands müssen Sie eine Referenz auf das Modulobjekt halten.
Normalerweise ist dies kein Problem, da Typen, die mit PyType_FromModuleAndSpec() erstellt wurden, und ihre Instanzen, eine Referenz auf das Modul halten. Sie müssen jedoch bei der Referenzzählung vorsichtig sein, wenn Sie von anderen Stellen, wie z. B. Rückrufen für externe Bibliotheken, auf den Modulzustaand verweisen.
Offene Probleme¶
Mehrere Probleme bezüglich Modul-spezifischem Zustand und Heap-Typen sind noch offen.
Diskussionen zur Verbesserung der Situation werden am besten im Diskussionsforum unter dem Tag c-api geführt.
Pro-Klassen-Bereich¶
Es ist derzeit (Stand Python 3.11) nicht möglich, individuelle Typen mit Zuständen zu versehen, ohne sich auf CPython-Implementierungsdetails zu verlassen (die sich in Zukunft ändern könnten – vielleicht ironischerweise, um eine ordnungsgemäße Lösung für den Pro-Klassen-Bereich zu ermöglichen).
Verlustfreie Konvertierung zu Heap-Typen¶
Die Heap-Typ-API wurde nicht für die „verlustfreie“ Konvertierung von statischen Typen entwickelt; das heißt, die Erstellung eines Typs, der exakt wie ein gegebener statischer Typ funktioniert.