Defining extension modules

Ein C-Erweiterungsmodul für CPython ist eine gemeinsam genutzte Bibliothek (z. B. eine .so-Datei unter Linux, eine .pyd-DLL unter Windows), die in den Python-Prozess geladen werden kann (z. B. sie wurde mit kompatiblen Compiler-Einstellungen kompiliert) und die eine Initialisierungsfunktion exportiert.

Um standardmäßig importierbar zu sein (d. h. durch importlib.machinery.ExtensionFileLoader), muss die gemeinsam genutzte Bibliothek in sys.path verfügbar sein und nach dem Modulnamen zuzüglich einer in importlib.machinery.EXTENSION_SUFFIXES aufgeführten Erweiterung benannt sein.

Hinweis

Das Erstellen, Verpacken und Verteilen von Erweiterungsmodulen wird am besten mit Drittanbieter-Tools durchgeführt und liegt außerhalb des Rahmens dieses Dokuments. Ein geeignetes Werkzeug ist Setuptools, dessen Dokumentation unter https://setuptools.pypa.io/en/latest/setuptools.html zu finden ist.

Normalerweise gibt die Initialisierungsfunktion eine Moduldefinition zurück, die mit PyModuleDef_Init() initialisiert wurde. Dies ermöglicht die Aufteilung des Erstellungsprozesses in mehrere Phasen.

  • Bevor wesentlicher Code ausgeführt wird, kann Python feststellen, welche Fähigkeiten das Modul unterstützt, und die Umgebung anpassen oder das Laden eines inkompatiblen Erweiterungsmoduls verweigern.

  • Standardmäßig erstellt Python selbst das Modulobjekt – das heißt, es führt das Äquivalent von object.__new__() für Klassen aus. Es setzt auch anfängliche Attribute wie __package__ und __loader__.

  • Anschließend wird das Modulobjekt mit erweiterungsspezifischem Code initialisiert – dem Äquivalent von __init__() für Klassen.

Dies wird als Multi-Phasen-Initialisierung bezeichnet, um sie von dem älteren (aber immer noch unterstützten) Single-Phasen-Initialisierungs-Schema zu unterscheiden, bei dem die Initialisierungsfunktion ein vollständig erstelltes Modul zurückgibt. Siehe den Abschnitt Single-Phasen-Initialisierung unten für Details.

Geändert in Version 3.5: Unterstützung für Multi-Phasen-Initialisierung hinzugefügt (PEP 489).

Mehrere Modulinstanzen

Standardmäßig sind Erweiterungsmodule keine Singletons. Wenn beispielsweise der Eintrag in sys.modules entfernt und das Modul erneut importiert wird, wird ein neues Modulobjekt erstellt und typischerweise mit neuen Methoden- und Typobjekten gefüllt. Das alte Modul unterliegt der normalen Garbage Collection. Dies spiegelt das Verhalten von reinen Python-Modulen wider.

Zusätzliche Modulinstanzen können in Sub-Interpretern oder nach einer Reinitialisierung der Python-Laufzeitumgebung (Py_Finalize() und Py_Initialize()) erstellt werden. In diesen Fällen würde das Teilen von Python-Objekten zwischen Modulinstanzen wahrscheinlich zu Abstürzen oder undefiniertem Verhalten führen.

Um solche Probleme zu vermeiden, sollte jede Instanz eines Erweiterungsmoduls *isoliert* sein: Änderungen an einer Instanz sollten sich nicht implizit auf andere auswirken, und aller vom Modul besessene Zustand, einschließlich Referenzen auf Python-Objekte, sollte spezifisch für eine bestimmte Modulinstanz sein. Weitere Details und eine praktische Anleitung finden Sie unter Isolating Extension Modules.

Ein einfacherer Weg, diese Probleme zu vermeiden, ist das Auslösen eines Fehlers bei wiederholter Initialisierung.

Alle Module sollen Sub-Interpreter unterstützen oder andernfalls explizit mangelnde Unterstützung signalisieren. Dies geschieht normalerweise durch Isolierung oder das Blockieren wiederholter Initialisierung, wie oben beschrieben. Ein Modul kann auch auf den Hauptinterpreter beschränkt werden, indem der Py_mod_multiple_interpreters-Slot verwendet wird.

Initialisierungsfunktion

Die von einem Erweiterungsmodul definierte Initialisierungsfunktion hat die folgende Signatur:

PyObject *PyInit_modulename(void)

Ihr Name sollte PyInit_<name> lauten, wobei <name> durch den Namen des Moduls ersetzt wird.

Für Module mit reinen ASCII-Namen muss die Funktion stattdessen PyInit_<name> heißen, wobei <name> durch den Namen des Moduls ersetzt wird. Bei Verwendung der Multi-Phasen-Initialisierung sind Nicht-ASCII-Modulnamen zulässig. In diesem Fall lautet der Name der Initialisierungsfunktion PyInitU_<name>, wobei <name> mit Pythons *punycode*-Kodierung mit durch Unterstriche ersetzten Bindestrichen kodiert ist. In Python

def initfunc_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + suffix

Es wird empfohlen, die Initialisierungsfunktion mit einem Hilfsmakro zu definieren:

PyMODINIT_FUNC

Deklariert eine Initialisierungsfunktion für Erweiterungsmodule. Dieses Makro

  • gibt den Rückgabetyp PyObject* an,

  • fügt alle vom System erforderlichen speziellen Linkage-Deklarationen hinzu und

  • deklariert für C++ die Funktion als extern "C".

Zum Beispiel würde ein Modul namens spam wie folgt definiert werden:

static struct PyModuleDef spam_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "spam",
    ...
};

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModuleDef_Init(&spam_module);
}

Es ist möglich, mehrere Module aus einer einzigen gemeinsam genutzten Bibliothek zu exportieren, indem mehrere Initialisierungsfunktionen definiert werden. Der Import erfordert jedoch die Verwendung von symbolischen Links oder eines benutzerdefinierten Importeurs, da standardmäßig nur die Funktion, die dem Dateinamen entspricht, gefunden wird. Weitere Einzelheiten finden Sie im Abschnitt Multiple modules in one library in PEP 489.

Die Initialisierungsfunktion ist typischerweise das einzige Nicht-static-Element, das im C-Quellcode des Moduls definiert wird.

Multi-Phasen-Initialisierung

Normalerweise gibt die Initialisierungsfunktion (PyInit_modulename) eine PyModuleDef-Instanz mit einem nicht-NULL m_slots zurück. Bevor sie zurückgegeben wird, muss die PyModuleDef-Instanz mit der folgenden Funktion initialisiert werden:

PyObject *PyModuleDef_Init(PyModuleDef *def)
Teil der Stable ABI seit Version 3.5.

Stellt sicher, dass eine Moduldefinition ein korrekt initialisiertes Python-Objekt ist, das seinen Typ und eine Referenzanzahl korrekt angibt.

Gibt *def* zurück, das nach PyObject* gecastet wurde, oder NULL, wenn ein Fehler aufgetreten ist.

Das Aufrufen dieser Funktion ist für die Multi-Phasen-Initialisierung erforderlich. Sie sollte in anderen Kontexten nicht verwendet werden.

Beachten Sie, dass Python davon ausgeht, dass PyModuleDef-Strukturen statisch alloziert sind. Diese Funktion kann eine neue Referenz oder eine geliehene Referenz zurückgeben; diese Referenz darf nicht freigegeben werden.

Hinzugefügt in Version 3.5.

Veraltete Single-Phasen-Initialisierung

Aufmerksamkeit

Die Single-Phasen-Initialisierung ist ein veralteter Mechanismus zur Initialisierung von Erweiterungsmodulen mit bekannten Nachteilen und Designfehlern. Autoren von Erweiterungsmodulen werden ermutigt, stattdessen die Multi-Phasen-Initialisierung zu verwenden.

Bei der Single-Phasen-Initialisierung sollte die Initialisierungsfunktion (PyInit_modulename) ein Modulobjekt erstellen, füllen und zurückgeben. Dies geschieht typischerweise mit PyModule_Create() und Funktionen wie PyModule_AddObjectRef().

Die Single-Phasen-Initialisierung unterscheidet sich von der Standardeinstellung in folgenden Punkten:

  • Single-Phasen-Module sind oder vielmehr *enthalten* "Singletons".

    Wenn das Modul zum ersten Mal initialisiert wird, speichert Python den Inhalt des __dict__ des Moduls (also typischerweise die Funktionen und Typen des Moduls).

    Bei nachfolgenden Importen ruft Python die Initialisierungsfunktion nicht erneut auf. Stattdessen erstellt es ein neues Modulobjekt mit einem neuen __dict__ und kopiert die gespeicherten Inhalte dorthin. Zum Beispiel, gegeben ein Single-Phasen-Modul _testsinglephase [1], das eine Funktion sum und eine Ausnahmeklasse error definiert:

    >>> import sys
    >>> import _testsinglephase as one
    >>> del sys.modules['_testsinglephase']
    >>> import _testsinglephase as two
    >>> one is two
    False
    >>> one.__dict__ is two.__dict__
    False
    >>> one.sum is two.sum
    True
    >>> one.error is two.error
    True
    

    Das genaue Verhalten sollte als Implementierungsdetail von CPython betrachtet werden.

  • Um das Problem zu umgehen, dass PyInit_modulename kein *spec*-Argument entgegennimmt, wird ein Teil des Zustands der Import-Maschinerie gespeichert und auf das erste geeignete Modul angewendet, das während des Aufrufs von PyInit_modulename erstellt wird. Insbesondere, wenn ein Untermodul importiert wird, fügt dieser Mechanismus dem Modulnamen den Namen des übergeordneten Pakets voran.

    Eine Single-Phasen-PyInit_modulename-Funktion sollte ihr Modulobjekt so früh wie möglich erstellen, bevor andere Modulobjekte erstellt werden können.

  • Nicht-ASCII-Modulnamen (PyInitU_modulename) werden nicht unterstützt.

  • Single-Phasen-Module unterstützen Modul-Lookup-Funktionen wie PyState_FindModule().