Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

Inhaltsverzeichnis

Wichtig

Dieses PEP ist ein historisches Dokument. Die aktuelle, kanonische Dokumentation finden Sie jetzt unter Isolating Extension Modules HOWTO.

×

Siehe PEP 1, um Änderungen vorzuschlagen.

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 384Defining a Stable ABI, das eine C-API zur Erstellung von Heap-Typen hinzufügte
  • PEP 489Multi-phase extension module initialization
  • PEP 573Module 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_EndInterpreter verwaltet 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 readline verwaltet *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_GC haben.
  • Eine Traversierungsfunktion mit Py_TP_TRAVERSE definieren, die den Typ besucht (z. B. mit Py_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.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0630.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT