Erweiterungsmodule isolieren
****************************


Zusammenfassung
^^^^^^^^^^^^^^^

Herkömmlich wurde Zustand, der zu Python-Erweiterungsmodulen gehört,
in C-"static"-Variablen gehalten, die prozessweit gelten. Dieses
Dokument beschreibt die Probleme solchen Zustands je Prozess und zeigt
einen sichereren Weg: Zustand je Modul.

Das Dokument beschreibt außerdem, wie du dort, wo es möglich ist, auf
Zustand je Modul umstellst. Dieser Übergang umfasst das Anfordern von
Platz für diesen Zustand, möglicherweise die Umstellung von statischen
Typen auf Halden-Typen und – vielleicht am wichtigsten – den Zugriff
auf den Zustand je Modul aus dem Code.


Für wen das gedacht ist
=======================

Diese Anleitung richtet sich an Betreuer von C-API-Erweiterungen, die
ihre Erweiterung sicherer machen wollen für Anwendungen, in denen
Python selbst als Bibliothek verwendet wird.


Hintergrund
===========

Ein *Interpreter* ist der Zusammenhang, in dem Python-Code läuft. Er
umfasst Konfiguration (etwa den Importpfad) und Laufzeitzustand (etwa
die Menge der importierten Module).

Python kann mehrere Interpreter in einem Prozess laufen lassen. Dabei
sind zwei Fälle zu bedenken – Benutzer können Interpreter laufen
lassen:

* hintereinander, mit mehreren
  "Py_InitializeEx()"/"Py_FinalizeEx()"-Durchläufen, und

* parallel, indem sie mit "Py_NewInterpreter()"/"Py_EndInterpreter()"
  „Unterinterpreter“ verwalten.

Beide Fälle (und Verbindungen davon) sind am nützlichsten, wenn Python
in eine Bibliothek eingebettet wird. Bibliotheken sollten im
Allgemeinen keine Annahmen über die Anwendung treffen, die sie
verwendet – dazu gehört die Annahme eines prozessweiten „Haupt-Python-
Interpreters“.

Historisch gehen Python-Erweiterungsmodule mit diesem Anwendungsfall
nicht gut um. Viele Erweiterungsmodule (und sogar einige Module der
Standardbibliothek) verwenden globalen Zustand *je Prozess*, weil
C-"static"-Variablen ausgesprochen leicht zu verwenden sind. So landen
Daten, die einem Interpreter eigen sein sollten, gemeinsam genutzt bei
allen Interpretern. Sofern der Entwickler der Erweiterung nicht
achtgibt, ist es sehr leicht, Grenzfälle einzuschleppen, die zu
Abstürzen führen, wenn ein Modul in mehr als einem Interpreter im
selben Prozess geladen wird.

Zustand *je Interpreter* ist leider nicht leicht zu erreichen. Autoren
von Erweiterungen denken beim Entwickeln meist nicht an mehrere
Interpreter, und das Verhalten zu testen ist derzeit umständlich.


Auftritt: Zustand je Modul
--------------------------

Anstatt sich auf Zustand je Interpreter zu stützen, entwickelt sich
die C-API von Python so, dass sie den feinkörnigeren Zustand *je
Modul* besser unterstützt. Das heißt, dass Daten auf C-Ebene an ein
*Modulobjekt* gebunden werden sollten. Jeder Interpreter erzeugt sein
eigenes Modulobjekt und hält die Daten damit getrennt. Um die
Isolation zu testen, lassen sich sogar mehrere Modulobjekte derselben
Erweiterung in einem einzigen Interpreter laden.

Zustand je Modul bietet einen einfachen Weg, über Lebensdauer und
Besitz von Ressourcen nachzudenken: Das Erweiterungsmodul
initialisiert sich, wenn ein Modulobjekt erzeugt wird, und räumt auf,
wenn es freigegeben wird. In dieser Hinsicht ist ein Modul wie jedes
andere PyObject*. Es gibt keine Haken für „beim Herunterfahren des
Interpreters“, an die man denken – oder die man vergessen – muss.

Beachte, dass es Anwendungsfälle für verschiedene Arten von „Globalen“
gibt: Zustand je Prozess, je Interpreter, je Thread oder je Aufgabe.
Mit Zustand je Modul als Vorgabe sind diese weiter möglich, du
solltest sie aber als Ausnahmefälle behandeln: Brauchst du sie,
solltest du ihnen zusätzliche Aufmerksamkeit und Tests widmen.
(Beachte, dass diese Anleitung sie nicht behandelt.)


Isolated Module Objects
-----------------------

Der wichtigste Punkt, den du beim Entwickeln eines Erweiterungsmoduls
im Kopf behalten solltest, ist: Aus einer einzigen dynamischen
Bibliothek können mehrere Modulobjekte erzeugt werden. 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 Daumenregel sollten die beiden Module vollständig unabhängig sein.
Alle Objekte und aller Zustand, die dem Modul eigen sind, sollten im
Modulobjekt eingekapselt sein, nicht mit anderen Modulobjekten geteilt
werden und aufgeräumt werden, wenn das Modulobjekt freigegeben wird.
Weil das nur eine Daumenregel ist, sind Ausnahmen möglich (siehe
Managing Global State), sie brauchen aber mehr Überlegung und
Aufmerksamkeit für Grenzfälle.

Manche Module kämen mit weniger strengen Einschränkungen aus, aber
isolierte Module machen es leichter, klare Erwartungen und Richtlinien
aufzustellen, die über eine Vielzahl von Anwendungsfällen hinweg
funktionieren.


Überraschende Grenzfälle
------------------------

Beachte, dass isolierte Module einige überraschende Grenzfälle mit
sich bringen. Am auffälligsten ist, dass jedes Modulobjekt seine
Klassen und Ausnahmen üblicherweise nicht mit anderen ähnlichen
Modulen teilt. Im Anschluss an das Beispiel oben ist zu beachten, dass
"old_binascii.Error" und "binascii.Error" getrennte Objekte sind. Im
folgenden Code wird die Ausnahme *nicht* gefangen:

   >>> 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

Das ist zu erwarten. Beachte, dass reine Python-Module sich genauso
verhalten: Es gehört dazu, wie Python arbeitet.

Das Ziel ist, Erweiterungsmodule auf C-Ebene sicher zu machen, nicht,
Tricks naheliegend zu machen. "sys.modules" „von Hand“ zu verändern,
zählt als Trick.


Module mit mehreren Interpretern sicher machen
==============================================


Managing Global State
---------------------

Manchmal ist der Zustand, der zu einem Python-Modul gehört, nicht
diesem Modul eigen, sondern dem ganzen Prozess (oder etwas anderem,
das „globaler“ ist als ein Modul). Zum Beispiel:

* Das Modul "readline" verwaltet *das* Terminal.

* Ein Modul, das auf einer Platine läuft, will *die* LED auf der
  Platine steuern.

In diesen Fällen sollte das Python-Modul *Zugriff* auf den globalen
Zustand bieten, anstatt ihn zu *besitzen*. Schreibe das Modul, wenn
möglich, so, dass mehrere Kopien davon unabhängig voneinander auf den
Zustand zugreifen können (ebenso wie andere Bibliotheken, ob für
Python oder für andere Sprachen). Ist das nicht möglich, erwäge
ausdrückliches Sperren.

Wenn prozessglobaler Zustand nötig ist, lassen sich Probleme mit
mehreren Interpretern am einfachsten vermeiden, indem du ausdrücklich
verhinderst, dass ein Modul mehr als einmal je Prozess geladen wird –
siehe Verzicht: Auf ein Modulobjekt je Prozess beschränken.


Zustand je Modul verwalten
--------------------------

Um Zustand je Modul zu verwenden, nimm die mehrphasige Initialisierung
von Erweiterungsmodulen. Damit zeigst du an, dass dein Modul mehrere
Interpreter korrekt unterstützt.

Setze "PyModuleDef.m_size" auf eine positive Zahl, um so viele Bytes
modullokalen Speicher anzufordern. Üblicherweise wird das auf die
Größe einer modulspezifischen "struct" gesetzt, die den gesamten
Zustand des Moduls auf C-Ebene aufnehmen kann. Dort solltest du
insbesondere Zeiger auf Klassen (einschließlich Ausnahmen, aber ohne
statische Typen) und Einstellungen ablegen (etwa "field_size_limit"
von "csv"), die der C-Code zum Arbeiten braucht.

Bemerkung:

  Eine andere Möglichkeit ist, den Zustand im "__dict__" des Moduls zu
  halten, aber du musst dann Abstürze vermeiden, wenn Benutzer
  "__dict__" aus Python-Code verändern. Das bedeutet üblicherweise
  Fehler- und Typprüfungen auf C-Ebene, die man leicht falsch macht
  und schwer ausreichend testen kann.Wird der Modulzustand im C-Code
  allerdings nicht gebraucht, ist es eine gute Idee, ihn nur im
  "__dict__" zu halten.

Enthält der Modulzustand "PyObject"-Zeiger, muss das Modulobjekt
Referenzen auf diese Objekte halten und die Haken auf Modulebene
"m_traverse", "m_clear" und "m_free" umsetzen. Diese arbeiten wie
"tp_traverse", "tp_clear" und "tp_free" einer Klasse. Sie zu ergänzen
bedeutet etwas Arbeit und macht den Code länger. Das ist der Preis für
Module, die sich sauber entladen lassen.

Ein Beispiel für ein Modul mit Zustand je Modul ist derzeit als
xxlimited verfügbar. Die Beispiel-Initialisierung des Moduls steht am
Ende der Datei.


Verzicht: Auf ein Modulobjekt je Prozess beschränken
----------------------------------------------------

Ein nicht negatives "PyModuleDef.m_size" zeigt an, dass ein Modul
mehrere Interpreter korrekt unterstützt. Ist das bei deinem Modul noch
nicht der Fall, kannst du es ausdrücklich nur einmal je Prozess ladbar
machen. 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" deines Moduls auf eine
künftige erneute Initialisierung vorbereiten kann, sollte sie das Flag
"loaded" zurücksetzen. In diesem Fall unterstützt dein Modul keine
mehreren Instanzen, die *gleichzeitig* bestehen, aber es unterstützt
zum Beispiel, nach dem Herunterfahren der Python-Laufzeit
("Py_FinalizeEx()") und erneuter Initialisierung ("Py_Initialize()")
geladen zu werden.


Zugriff auf den Modulzustand aus Funktionen
-------------------------------------------

Auf den Zustand aus Funktionen auf Modulebene zuzugreifen, ist
einfach. Funktionen bekommen das Modulobjekt als erstes Argument. Um
den Zustand herauszuholen, kannst du "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
   }

Bemerkung:

  "PyModule_GetState" kann "NULL" zurückgeben, ohne eine Ausnahme zu
  setzen, wenn es keinen Modulzustand gibt, also "PyModuleDef.m_size"
  null war. In deinem eigenen Modul hast du "m_size" in der Hand, das
  lässt sich also leicht verhindern.


Halden-Typen
============

Herkömmlich sind in C-Code definierte Typen *statisch*, also "static
PyTypeObject"-Strukturen, die unmittelbar im Code definiert und mit
"PyType_Ready()" initialisiert werden.

Solche Typen werden notwendigerweise prozessweit geteilt. Sie zwischen
Modulobjekten zu teilen bedeutet, auf jeden Zustand achtzugeben, den
sie besitzen oder auf den sie zugreifen. Um die möglichen Probleme zu
begrenzen, sind statische Typen auf Python-Ebene unveränderlich: Du
kannst zum Beispiel nicht "str.myattribute = 123" setzen.

Wirklich unveränderliche Objekte zwischen Interpretern zu teilen ist
in Ordnung, solange sie keinen Zugriff auf veränderliche Objekte
bieten. In CPython hat jedes Python-Objekt jedoch ein veränderliches
Implementierungsdetail: den Referenzzähler. Änderungen am
Referenzzähler sind durch den GIL abgesichert. Code, der irgendwelche
Python-Objekte über Interpreter hinweg teilt, hängt damit implizit vom
aktuellen, prozessweiten GIL von CPython ab.

Weil sie unveränderlich und prozessglobal sind, können statische Typen
nicht auf „ihren“ Modulzustand zugreifen. Wenn irgendeine Methode
eines solchen Typs Zugriff auf den Modulzustand braucht, muss der Typ
in einen *auf der Halde angelegten Typ* umgewandelt werden, kurz
*Halden-Typ*. Diese entsprechen Klassen, die mit der "class"-Anweisung
von Python erzeugt werden, deutlich genauer.

Für neue Module ist es eine gute Daumenregel, standardmäßig Halden-
Typen zu verwenden.


Statische Typen in Halden-Typen umwandeln
-----------------------------------------

Statische Typen lassen sich in Halden-Typen umwandeln, aber beachte,
dass die API für Halden-Typen nicht für eine „verlustfreie“ Umwandlung
aus statischen Typen entworfen wurde – also dafür, einen Typ zu
erzeugen, der sich genau wie ein gegebener statischer Typ verhält.
Wenn du die Klassendefinition in einer neuen API neu schreibst,
änderst du daher wahrscheinlich unbeabsichtigt ein paar Details (etwa
die Pickle-Fähigkeit oder geerbte Slots). Teste immer die Details, die
dir wichtig sind.

Achte besonders auf die folgenden zwei Punkte (aber beachte, dass dies
keine vollständige Liste ist):

* Anders als statische Typen sind Halden-Typobjekte standardmäßig
  veränderlich. Verwende das Flag "Py_TPFLAGS_IMMUTABLETYPE", um die
  Veränderlichkeit zu verhindern.

* Halden-Typen erben standardmäßig "tp_new", es kann also möglich
  werden, sie aus Python-Code zu erzeugen. Das kannst du mit dem Flag
  "Py_TPFLAGS_DISALLOW_INSTANTIATION" verhindern.


Halden-Typen definieren
-----------------------

Halden-Typen lassen sich erzeugen, indem du eine
"PyType_Spec"-Struktur füllst – eine Beschreibung oder „Blaupause“
einer Klasse – und "PyType_FromModuleAndSpec()" aufrufst, um ein neues
Klassenobjekt zu bauen.

Bemerkung:

  Andere Funktionen wie "PyType_FromSpec()" können ebenfalls Halden-
  Typen erzeugen, aber "PyType_FromModuleAndSpec()" verknüpft das
  Modul mit der Klasse und erlaubt damit den Zugriff auf den
  Modulzustand aus Methoden.

Die Klasse sollte im Allgemeinen *sowohl* im Modulzustand (für den
sicheren Zugriff aus C) *als auch* im "__dict__" des Moduls (für den
Zugriff aus Python-Code) abgelegt werden.


Das Protokoll der Speicherbereinigung
-------------------------------------

Instanzen von Halden-Typen halten eine Referenz auf ihren Typ. Das
stellt sicher, dass der Typ nicht vor allen seinen Instanzen zerstört
wird, kann aber zu Referenzzyklen führen, die die Speicherbereinigung
auflösen muss.

Um Speicherlecks zu vermeiden, müssen Instanzen von Halden-Typen das
Protokoll der Speicherbereinigung umsetzen. Halden-Typen sollten also:

* das Flag "Py_TPFLAGS_HAVE_GC" haben.

* eine traverse-Funktion über "Py_tp_traverse" definieren, die den Typ
  besucht (etwa über "Py_VISIT(Py_TYPE(self))").

Weitere Überlegungen findest du in der Dokumentation zu
"Py_TPFLAGS_HAVE_GC" und "tp_traverse".

Die API zum Definieren von Halden-Typen ist organisch gewachsen,
wodurch sie in ihrem heutigen Zustand etwas unhandlich ist. Die
folgenden Abschnitte führen dich durch die verbreiteten Probleme.


"tp_traverse" in Python 3.8 und älter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Die Anforderung, den Typ aus "tp_traverse" zu besuchen, wurde in
Python 3.9 ergänzt. Wenn du Python 3.8 und älter unterstützt, darf die
traverse-Funktion den Typ *nicht* besuchen, sie muss also
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 ergänzt. Verwende als
Ersatz:

* "PY_VERSION_HEX", sofern du nicht das stabile ABI verwendest, oder

* "sys.version_info" (über "PySys_GetObject()" und
  "PyArg_ParseTuple()").


"tp_traverse" weitergeben
~~~~~~~~~~~~~~~~~~~~~~~~~

Wenn deine traverse-Funktion an "tp_traverse" ihrer Basisklasse (oder
eines anderen Typs) weitergibt, stelle sicher, dass "Py_TYPE(self)"
nur einmal besucht wird. Beachte, dass nur von Halden-Typen erwartet
wird, den Typ in "tp_traverse" zu besuchen.

Wenn deine traverse-Funktion zum Beispiel Folgendes enthält:

   base->tp_traverse(self, visit, arg)

… und "base" ein statischer Typ sein kann, dann sollte sie außerdem
Folgendes enthalten:

   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));
       }
   }

Den Referenzzähler des Typs in "tp_new" und "tp_clear" zu behandeln,
ist nicht nötig.


"tp_dealloc" definieren
~~~~~~~~~~~~~~~~~~~~~~~

Wenn dein Typ eine eigene "tp_dealloc"-Funktion hat, muss sie:

* "PyObject_GC_UnTrack()" aufrufen, bevor irgendwelche Felder ungültig
  werden, und

* den Referenzzähler des Typs verringern.

Damit der Typ gültig bleibt, während "tp_free" aufgerufen wird, muss
der Referenzzähler des Typs *nach* der Freigabe der Instanz verringert
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 Standardfunktion "tp_dealloc" tut das, wenn dein Typ "tp_dealloc"
also *nicht* überschreibt, brauchst du es nicht zu ergänzen.


"tp_free" nicht überschreiben
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Der Slot "tp_free" eines Halden-Typs muss auf "PyObject_GC_Del()"
gesetzt sein. Das ist die Vorgabe. Überschreibe sie nicht.


"PyObject_New" vermeiden
~~~~~~~~~~~~~~~~~~~~~~~~

Objekte, die von der Speicherbereinigung verfolgt werden, müssen über
Funktionen angefordert werden, die die Speicherbereinigung kennen.

Wenn du "PyObject_New()" oder "PyObject_NewVar()" verwendest:

* Hole und rufe, wenn möglich, den Slot "tp_alloc" des Typs auf.
  Ersetze also "TYPE *o = PyObject_New(TYPE, typeobj)" durch:

     TYPE *o = typeobj->tp_alloc(typeobj, 0);

  Ersetze "o = PyObject_NewVar(TYPE, typeobj, size)" durch dasselbe,
  verwende aber size anstelle der 0.

* Ist das oben Genannte nicht möglich (etwa innerhalb eines eigenen
  "tp_alloc"), rufe "PyObject_GC_New()" oder "PyObject_GC_NewVar()"
  auf:

     TYPE *o = PyObject_GC_New(TYPE, typeobj);

     TYPE *o = PyObject_GC_NewVar(TYPE, typeobj, size);


Zugriff auf den Modulzustand aus Klassen
----------------------------------------

Wenn du ein Typobjekt hast, das mit "PyType_FromModuleAndSpec()"
definiert wurde, kannst du "PyType_GetModule()" aufrufen, um das
zugehörige Modul zu bekommen, und dann "PyModule_GetState()", um den
Zustand des Moduls zu bekommen.

Um ein paar Zeilen mühsamen Fehlerbehandlungscodes zu sparen, kannst
du diese zwei Schritte mit "PyType_GetModuleState()" verbinden. Das
ergibt:

   my_struct *state = (my_struct*)PyType_GetModuleState(type);
   if (state == NULL) {
       return NULL;
   }


Zugriff auf den Modulzustand aus gewöhnlichen Methoden
------------------------------------------------------

Auf den Zustand auf Modulebene aus Methoden einer Klasse zuzugreifen,
ist etwas komplizierter, aber dank einer in Python 3.9 eingeführten
API möglich. Um an den Zustand zu kommen, musst du zuerst die
*definierende Klasse* holen und daraus dann den Modulzustand.

Die größte Hürde ist, *die Klasse zu bekommen, in der eine Methode
definiert wurde*, kurz die „definierende Klasse“ dieser Methode. Die
definierende Klasse kann eine Referenz auf das Modul haben, zu dem sie
gehört.

Verwechsle die definierende Klasse nicht mit "Py_TYPE(self)". Wird die
Methode auf einer *Unterklasse* deines Typs aufgerufen, verweist
"Py_TYPE(self)" auf diese Unterklasse, die in einem anderen Modul als
deinem definiert sein kann.

Bemerkung:

  Der folgende Python-Code kann das Konzept veranschaulichen.
  "Base.get_defining_class" gibt "Base" zurück, selbst wenn
  "type(self) == Sub" ist:

     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“ bekommt, 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 du die definierende Klasse hast, rufe "PyType_GetModuleState()"
auf, um den Zustand des zugehörigen Moduls zu bekommen.

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 den Modulzustand aus Slot-Methoden, Lese- und Schreibfunktionen
---------------------------------------------------------------------------

Bemerkung:

  Das ist neu in Python 3.11.

Slot-Methoden – die schnellen C-Gegenstücke zu den besonderen
Methoden, etwa "nb_add" für "__add__" oder "tp_new" für die
Initialisierung – haben eine sehr einfache API, die es anders als bei
"PyCMethod" nicht erlaubt, die definierende Klasse hineinzugeben.
Dasselbe gilt für Lese- und Schreibfunktionen, die mit "PyGetSetDef"
definiert werden.

Um in diesen Fällen auf den Modulzustand zuzugreifen, verwende die
Funktion "PyType_GetModuleByDef()" und gib die Moduldefinition hinein.
Sobald du das Modul hast, rufe "PyModule_GetState()" auf, um an den
Zustand zu kommen:

   PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &module_def);
   my_struct *state = (my_struct*)PyModule_GetState(module);
   if (state == NULL) {
       return NULL;
   }

"PyType_GetModuleByDef()" arbeitet, indem es die
*Methodenauflösungsreihenfolge* (also alle Oberklassen) nach der
ersten Oberklasse durchsucht, die ein zugehöriges Modul hat.

Bemerkung:

  In sehr exotischen Fällen (Vererbungsketten über mehrere Module
  hinweg, die aus derselben Definition erzeugt wurden) gibt
  "PyType_GetModuleByDef()" möglicherweise nicht das Modul der
  wirklich definierenden Klasse zurück. Es gibt aber immer ein Modul
  mit derselben Definition zurück und stellt damit eine kompatible
  C-Speicheranordnung sicher.


Lebensdauer des Modulzustands
-----------------------------

Wird ein Modulobjekt von der Speicherbereinigung eingesammelt, wird
sein Modulzustand freigegeben. Für jeden Zeiger auf (einen Teil des)
Modulzustand musst du eine Referenz auf das Modulobjekt halten.

Üblicherweise ist das kein Problem, weil Typen, die mit
"PyType_FromModuleAndSpec()" erzeugt wurden, und ihre Instanzen eine
Referenz auf das Modul halten. Du musst bei der Referenzzählung aber
achtgeben, wenn du den Modulzustand von anderen Stellen aus
referenzierst, etwa aus Rückrufen für externe Bibliotheken.


Offene Fragen
=============

Rund um Zustand je Modul und Halden-Typen sind noch mehrere Fragen
offen.

Erörterungen darüber, wie sich die Lage verbessern lässt, finden am
besten im Diskussionsforum unter dem Schlagwort c-api statt.


Gültigkeitsbereich je Klasse
----------------------------

Derzeit (Stand Python 3.11) ist es nicht möglich, Zustand an einzelne
*Typen* zu binden, ohne sich auf Implementierungsdetails von CPython
zu stützen (die sich künftig ändern können – vielleicht,
ironischerweise, um eine saubere Lösung für den Gültigkeitsbereich je
Klasse zu erlauben).


Verlustfreie Umwandlung in Halden-Typen
---------------------------------------

Die API für Halden-Typen wurde nicht für eine „verlustfreie“
Umwandlung aus statischen Typen entworfen, also dafür, einen Typ zu
erzeugen, der sich genau wie ein gegebener statischer Typ verhält.
