PEP 630 – Isolating Extension Modules
- Autor:
- Petr Viktorin <encukou at gmail.com>
- Discussions-To:
- Capi-SIG list
- Status:
- Final
- Typ:
- Informational
- Erstellt:
- 25. Aug. 2020
- Post-History:
- 16-Jul-2020
Zusammenfassung
Traditionell wurde der Zustand, der zu Python-Erweiterungsmodulen gehört, in C static-Variablen gehalten, die einen prozessweiten Gültigkeitsbereich haben. Dieses Dokument beschreibt Probleme eines solchen prozessweiten Zustands und Bemühungen, Modul-spezifischen Zustand – eine bessere Standardeinstellung – möglich und einfach nutzbar zu machen.
Das Dokument beschreibt auch, wie man zu Modul-spezifischem Zustand wechselt, wo immer dies möglich ist. Dieser Übergang beinhaltet die Zuweisung von Speicherplatz für diesen Zustand, möglicherweise den Wechsel von statischen Typen zu Heap-Typen und – vielleicht am wichtigsten – den Zugriff auf Modul-spezifischen Zustand aus dem Code.
Über dieses Dokument
Als informatives PEP führt dieses Dokument keine Änderungen ein; diese sollten in eigenen PEPs (oder Issues, wenn klein genug) erfolgen. Vielmehr behandelt es die Motivation hinter einer Anstrengung, die sich über mehrere Releases erstreckt, und weist Early Adopters an, wie sie die abgeschlossenen Features nutzen können.
Sobald die Unterstützung einigermaßen vollständig ist, können diese Inhalte als HOWTO in die Python-Dokumentation verschoben werden. In der Zwischenzeit können Lücken, die in diesem PEP identifiziert werden, im Geiste der dokumentationsgesteuerten Entwicklung zeigen, wo der Fokus der Anstrengung liegen sollte, und es kann aktualisiert werden, wenn neue Features implementiert werden.
Immer wenn dieses PEP *Erweiterungsmodule* erwähnt, gilt der Ratschlag auch für *eingebaute* Module.
Hinweis
Dieses PEP enthält allgemeine Ratschläge. Berücksichtigen Sie bei der Befolgung immer die Besonderheiten Ihres Projekts.
Zum Beispiel, obwohl viele dieser Ratschläge für die C-Teile der Python-Standardbibliothek gelten, berücksichtigt das PEP keine Besonderheiten der Standardbibliothek (ungewöhnliche Rückwärtskompatibilitätsprobleme, Zugriff auf private APIs usw.).
PEPs, die sich auf diese Bemühung beziehen, sind
- PEP 384 – Defining a Stable ABI, das eine C-API zur Erstellung von Heap-Typen hinzufügte
- PEP 489 – Multi-phase extension module initialization
- PEP 573 – Module State Access from C Extension Methods
Dieses Dokument befasst sich mit der öffentlichen C-API von Python, die nicht von allen Implementierungen von Python angeboten wird. Nichts in diesem PEP ist jedoch spezifisch für CPython.
Wie bei jedem informativen PEP stellt dieser Text nicht notwendigerweise einen Konsens oder eine Empfehlung der Python-Community dar.
Motivation
Ein *Interpreter* ist der Kontext, in dem Python-Code ausgeführt wird. Er enthält Konfigurationen (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 und - parallel, indem "Sub-Interpreter" mit
Py_NewInterpreter/Py_EndInterpreterverwaltet werden.
Beide Fälle (und Kombinationen davon) wären am nützlichsten beim Einbetten von Python in eine Bibliothek. Bibliotheken sollten generell keine Annahmen über die Anwendung treffen, die sie verwendet, was die Annahme eines prozessweiten "Haupt-Python-Interpreters" einschließt.
Derzeit handhabt CPython diesen Anwendungsfall nicht gut. Viele Erweiterungsmodule (und sogar einige Standardbibliotheksmodule) verwenden einen *prozessweiten* globalen Zustand, da C static-Variablen extrem einfach zu verwenden sind. Daher werden Daten, die für einen Interpreter spezifisch sein sollten, zwischen Interpretern geteilt. Wenn der Erweiterungsentwickler nicht vorsichtig ist, ist es sehr einfach, Grenzfälle einzuführen, die zu Abstürzen führen, wenn ein Modul in mehr als einem Interpreter im selben Prozess geladen wird.
Leider ist *Interpreter-spezifischer* Zustand nicht einfach zu erreichen – Erweiterungsautoren neigen dazu, mehrere Interpreter bei der Entwicklung nicht im Auge zu behalten, und es ist derzeit umständlich, das Verhalten zu testen.
Begründung für Modul-spezifischen Zustand
Anstatt sich auf Interpreter-spezifischen Zustand zu konzentrieren, entwickelt sich die C-API von Python weiter, um den feingranulareren *Modul-spezifischen* Zustand besser zu unterstützen. Standardmäßig werden C-Level-Daten an ein *Modulobjekt* angehängt. Jeder Interpreter erstellt dann sein eigenes Modulobjekt und hält die Daten getrennt. Zum Testen der Isolierung können sogar mehrere Modulobjekte, die einer einzelnen Erweiterung entsprechen, in einem einzigen Interpreter geladen werden.
Modul-spezifischer Zustand bietet eine einfache Möglichkeit, über Lebensdauer und Ressourcenbesitz nachzudenken: Das Erweiterungsmodul wird initialisiert, wenn ein Modulobjekt erstellt wird, und bereinigt, wenn es deallokiert wird. In dieser Hinsicht ist ein Modul wie jedes andere PyObject *; es gibt keine "bei Interpreter-Beendigung"-Hooks, über die man nachdenken (oder die man vergessen) muss.
Ziel: Einfach zu verwendender Modulzustand
Es ist derzeit umständlich oder unmöglich, alles zu tun, was die C-API bietet, während Module isoliert bleiben. Ermöglicht durch PEP 384, zielen Änderungen in PEP 489 und PEP 573 (und zukünftige geplante) darauf ab, es *möglich* zu machen, Module auf diese Weise zu bauen, und dann *einfach* zu machen, neue Module auf diese Weise zu schreiben und alte zu konvertieren, damit es ein natürlicher Standard werden kann.
Selbst wenn Modul-spezifischer Zustand zum Standard wird, wird es Anwendungsfälle für verschiedene Ebenen der Kapselung geben: prozess-, pro-Interpreter-, pro-Thread- oder pro-Aufgaben-Zustand. Das Ziel ist, diese als Ausnahmefälle zu behandeln: Sie sollten möglich sein, aber Erweiterungsautoren müssen sorgfältiger darüber nachdenken.
Keine Ziele: Geschwindigkeitssteigerungen und die GIL
Es gibt einige Bemühungen, CPython auf Multi-Core-CPUs zu beschleunigen, indem die GIL pro Interpreter verwaltet wird. Während die Isolierung von Interpretern diese Bemühungen unterstützt, wird die Standardeinstellung auf Modul-spezifischen Zustand auch dann vorteilhaft sein, wenn keine Geschwindigkeitssteigerung erzielt wird, da sie die Unterstützung mehrerer Interpreter standardmäßig sicherer macht.
Module sicher machen mit mehreren Interpretern
Es gibt viele Möglichkeiten, mehrere Interpreter in Erweiterungsmodulen korrekt zu unterstützen. Der Rest dieses Textes beschreibt die bevorzugte Methode, ein solches Modul zu schreiben oder ein bestehendes zu konvertieren.
Beachten Sie, dass die Unterstützung ein laufendes Projekt ist; die API für einige Features, die Ihr Modul benötigt, ist möglicherweise noch nicht fertig.
Ein vollständiges Beispielmodul ist als xxlimited verfügbar.
Dieser Abschnitt geht davon aus, dass "Sie" ein Autor eines Erweiterungsmoduls sind.
Isolierte Modulobjekte
Der wichtigste Punkt, den Sie bei der Entwicklung eines Erweiterungsmoduls beachten sollten, ist, dass aus einer einzelnen gemeinsam genutzten Bibliothek mehrere Modulobjekte 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 für das Modul spezifisch sind, sollten innerhalb des Modulobjekts gekapselt, nicht mit anderen Modulobjekten geteilt und beim Löschen des Modulobjekts bereinigt werden. Ausnahmen sind möglich (siehe Verwaltung globalen Zustands), aber sie erfordern mehr Gedanken und Aufmerksamkeit für Grenzfälle als Code, der dieser Faustregel folgt.
Obwohl einige Module mit weniger strengen Einschränkungen auskommen könnten, erleichtern isolierte Module es, klare Erwartungen (und Richtlinien) festzulegen, die über eine Vielzahl von Anwendungsfällen hinweg funktionieren.
Überraschende Grenzfälle
Beachten Sie, dass isolierte Module einige überraschende Grenzfälle erzeugen. Am bemerkenswertesten ist, dass jedes Modulobjekt seine Klassen und Ausnahmen typischerweise nicht mit anderen ähnlichen Modulen teilt. Wenn wir von dem Beispiel oben ausgehen, 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 beabsichtigt. Beachten Sie, dass rein-python-Module das Gleiche verhalten: Es ist Teil der Funktionsweise von Python.
Das Ziel ist es, Erweiterungsmodule auf C-Ebene sicher zu machen, nicht Hacks intuitiv zu machen. Das "manuelle" Modifizieren von sys.modules zählt als Hack.
Verwaltung globalen Zustands
Manchmal ist der Zustand eines Python-Moduls 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 *eingebaute* LED steuern.
In diesen Fällen sollte das Python-Modul *Zugriff* auf den globalen Zustand bereitstellen, 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 dies 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: Beschränkung auf ein Modulobjekt pro Prozess.
Verwaltung Modul-spezifischen Zustands
Um Modul-spezifischen Zustand zu verwenden, nutzen Sie die Multi-Phase-Initialisierung von Erweiterungsmodulen, die in PEP 489 eingeführt wurde. Dies signalisiert, dass Ihr Modul mehrere Interpreter korrekt unterstützt.
Setzen Sie PyModuleDef.m_size auf eine positive Zahl, um so viele Bytes 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 statischer Typen) und Einstellungen (z. B. die field_size_limit von csv) speichern, die der C-Code zur Funktion 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 Fehler- und Typüberprüfung auf C-Ebene, was leicht falsch gemacht werden kann und schwer ausreichend zu testen ist.
Wenn der Modulzustand 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 ähnlich wie tp_traverse, tp_clear und tp_free einer Klasse. Ihre Hinzufügung erfordert einige Arbeit und verlängert den Code; dies ist der Preis für Module, die sauber entladen werden können.
Ein Beispiel für ein Modul mit Modul-spezifischem Zustand ist derzeit als xxlimited verfügbar; die Beispielmodulinitialisierung ist am Ende der Datei zu sehen.
Opt-Out: Beschränkung auf ein Modulobjekt pro Prozess
Eine nicht-negative 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 ladbar machen. Zum Beispiel
static int loaded = 0;
static int
exec_module(PyObject* module)
{
if (loaded) {
PyErr_SetString(PyExc_ImportError,
"cannot load module more than once per process");
return -1;
}
loaded = 1;
// ... rest of initialization
}
Zugriff auf Modulzustand aus Funktionen
Der Zugriff auf den Zustand aus Funktionen auf Modulebene ist unkompliziert. Funktionen erhalten das Modulobjekt als erstes Argument; zur Extraktion 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 Modulzustand vorhanden ist, d. h. PyModuleDef.m_size war Null. In Ihrem eigenen Modul haben Sie die Kontrolle über m_size, sodass dies leicht zu verhindern ist.
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 werden zwangsläufig prozessweit gemeinsam genutzt. Ihre gemeinsame Nutzung zwischen Modulobjekten erfordert Aufmerksamkeit für jeglichen 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 str.myattribute = 123 nicht setzen.
Hinweis
Die gemeinsame Nutzung wirklich unveränderlicher Objekte zwischen Interpretern ist in Ordnung, solange sie keinen Zugriff auf veränderliche Objekte bieten. In CPython hat jedoch jedes Python-Objekt eine veränderliche Implementierungsdetails: die Referenzzählung. Änderungen an der Referenzzählung werden von der GIL geschützt. Daher hängt Code, der beliebige Python-Objekte über Interpreter hinweg gemeinsam nutzt, implizit von der aktuellen, prozessweiten GIL von CPython ab.
Da sie unveränderlich und prozessweit global sind, können statische Typen nicht auf "ihren" Modulzustand zugreifen. Wenn eine Methode eines solchen Typs Zugriff auf den Modulzustand benötigt, muss der Typ in einen *heap-allokierten Typ* oder kurz *Heap-Typ* konvertiert werden. Diese entsprechen eher Klassen, die mit der class-Anweisung von Python erstellt wurden.
Für neue Module ist die Verwendung von Heap-Typen als Standard eine gute Faustregel.
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 konzipiert wurde – das heißt, die Erstellung eines Typs, der sich exakt wie ein gegebener statischer Typ verhält. Im Gegensatz zu statischen Typen sind Heap-Typ-Objekte standardmäßig veränderlich. Außerdem ändern Sie wahrscheinlich unabsichtlich einige Details (z. B. Pickle-Fähigkeit oder vererbte Slots), wenn Sie die Klassendefinition mit einer neuen API neu schreiben. Testen Sie immer die Details, die für Sie wichtig sind.
Definition von Heap-Typen
Heap-Typen können durch Ausfüllen einer PyType_Spec-Struktur, einer Beschreibung oder einer "Blaupause" einer Klasse, und durch Aufruf von PyType_FromModuleAndSpec() zur Konstruktion eines neuen Klassenobjekts erstellt werden.
Hinweis
Andere Funktionen, wie PyType_FromSpec(), können ebenfalls Heap-Typen erstellen, aber PyType_FromModuleAndSpec() ordnet das Modul der Klasse zu und ermöglicht so den Zugriff auf den Modulzustand aus Methoden.
Die Klasse sollte im Allgemeinen *sowohl* im Modulzustand (für sicheren Zugriff von 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 zerstört sind, kann aber zu Referenzzyklen führen, die vom Garbage Collector unterbrochen 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 Traversierungsfunktion mit
Py_TP_TRAVERSEdefinieren, die den Typ besucht (z. B. mitPy_VISIT(Py_TYPE(self));).
Bitte beachten Sie die Dokumentation von Py_TPFLAGS_HAVE_GC und tp_traverse <https://docs.pythonlang.de/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse> für zusätzliche Überlegungen.
Wenn Ihre Traversierungsfunktion 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 erwarten, den Typ in tp_traverse zu besuchen.
Zum Beispiel, wenn Ihre Traversierungsfunktion enthält
base->tp_traverse(self, visit, arg)
…und base ein statischer Typ sein kann, dann sollte sie auch enthalten
if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
// a heap type's tp_traverse already visited Py_TYPE(self)
} else {
Py_VISIT(Py_TYPE(self));
}
Es ist nicht notwendig, die Referenzzählung des Typs in tp_new und tp_clear zu behandeln.
Zugriff auf Modulzustand 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, um den Modulzustand zu erhalten.
Um mühsame Boilerplate-Code für die Fehlerbehandlung zu sparen, können Sie diese beiden Schritte mit PyType_GetModuleState kombinieren, was zu folgendem führt:
my_struct *state = (my_struct*)PyType_GetModuleState(type);
if (state === NULL) {
return NULL;
}
Zugriff auf Modulzustand aus regulären Methoden
Der Zugriff auf den Modulzustand aus Methoden einer Klasse ist etwas komplizierter, aber dank der in PEP 573 eingeführten Änderungen möglich. Um den Zustand zu erhalten, müssen Sie zuerst die *definierende Klasse* erhalten und dann von ihr den Modulzustand abrufen.
Die größte Hürde ist das Erhalten der *Klasse, in der eine Methode definiert wurde*, oder kurz gesagt, die "definierende 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 möglicherweise in einem anderen Modul als Ihrem definiert ist.
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_defining_class(self):
return __class__
class Sub(Base):
pass
Damit eine Methode ihre "definierende Klasse" erhält, muss sie die METH_METHOD | METH_FASTCALL | METH_KEYWORDS-Aufrufkonvention 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 Modulzustand aus Slot-Methoden, Getter und Setter
Hinweis
Dies ist neu in Python 3.11.
Slot-Methoden – die schnellen C-Äquivalente für spezielle Methoden, wie nb_add für __add__ oder tp_new für die Initialisierung – haben eine sehr einfache API, die das Übergeben der definierenden Klasse nicht erlaubt, im Gegensatz zu PyCMethod. Das Gleiche gilt für Getter und Setter, die mit PyGetSetDef definiert wurden.
Um in diesen Fällen auf den Modulzustand 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 MRO (d. h. alle Superklassen) durchsucht, um die erste Superklasse zu finden, die ein entsprechendes Modul hat.
Hinweis
In sehr exotischen Fällen (Vererbungsketten, die mehrere aus derselben Definition erstellte Module umfassen) gibt PyType_GetModuleByDef möglicherweise nicht das Modul der tatsächlichen definierenden Klasse zurück. Es gibt jedoch immer ein Modul mit derselben Definition zurück und gewährleistet so ein kompatibles C-Speicherlayout.
Lebensdauer des Modulzustands
Wenn ein Modulobjekt garbage collected wird, wird sein Modulzustand freigegeben. Für jeden Zeiger auf (einen Teil) des Modulzustands 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 Modulzustand von anderen Orten aus referenzieren, z. B. bei Rückrufen für externe Bibliotheken.
Offene Fragen
Mehrere Probleme im Zusammenhang mit Modul-spezifischem Zustand und Heap-Typen sind noch offen.
Diskussionen zur Verbesserung der Situation werden am besten auf der capi-sig Mailingliste geführt.
Typüberprüfung
Derzeit (Stand Python 3.10) haben Heap-Typen keine gute API, um Py*_Check-Funktionen (wie PyUnicode_Check für str, einen statischen Typ) zu schreiben, und daher ist es nicht einfach sicherzustellen, dass Instanzen ein bestimmtes C-Layout haben.
Metaklassen
Derzeit (Stand Python 3.10) gibt es keine gute API, um die *Metaklasse* eines Heap-Typs anzugeben; das heißt, das Feld ob_type des Typobjekts.
Pro-Klassen-Gültigkeitsbereich
Es ist auch nicht möglich, Zustand an *Typen* anzuhängen. Obwohl PyHeapTypeObject ein Objekt variabler Größe (PyVarObject) ist, wird sein Speicher variabler Größe derzeit von Slots belegt. Die Behebung dieses Problems wird durch die Tatsache erschwert, dass mehrere Klassen in einer Vererbungshierarchie möglicherweise etwas Zustand reservieren müssen.
Verlustfreie Konvertierung in Heap-Typen
Die Heap-Typ-API war nicht für eine "verlustfreie" Konvertierung von statischen Typen ausgelegt; das heißt, die Erstellung eines Typs, der sich exakt wie ein gegebener statischer Typ verhält. Die beste Lösung wäre wahrscheinlich, einen Leitfaden zu schreiben, der bekannte "Fallstricke" abdeckt.
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0630.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT