PEP 3121 – Initialisierung und Finalisierung von Erweiterungsmodulen
- Autor:
- Martin von Löwis <martin at v.loewis.de>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 27-Apr-2007
- Python-Version:
- 3.0
- Post-History:
Zusammenfassung
Die Initialisierung von Erweiterungsmodulen weist derzeit einige Mängel auf. Es gibt keine Bereinigung für Module, der Name des Einstiegspunkts kann zu Namenskonflikten führen, die Einstiegsfunktionen folgen nicht der üblichen Aufrufkonvention und mehrere Interpreter werden nicht gut unterstützt. Dieses PEP befasst sich mit diesen Problemen.
Probleme
Modul-Finalisierung
Derzeit werden Erweiterungsmodule normalerweise einmal initialisiert und dann "ewig" beibehalten. Die einzige Ausnahme ist, wenn Py_Finalize() aufgerufen wird: dann wird die Initialisierungsroutine ein zweites Mal aufgerufen. Dies ist aus Sicht der Ressourcenverwaltung schlecht: Speicher und andere Ressourcen könnten jedes Mal, wenn die Initialisierung aufgerufen wird, alloziert werden, aber es gibt keine Möglichkeit, sie zurückzugewinnen. Folglich gibt es derzeit keine Möglichkeit, alle von Python allozierten Ressourcen vollständig freizugeben.
Namenskonflikte bei Einstiegspunkten
Der Einstiegspunkt heißt derzeit init<modul>. Dies kann mit anderen Symbolen in Konflikt geraten, die ebenfalls init<etwas> heißen. Insbesondere ist bekannt, dass initsocket in der Vergangenheit Konflikte verursacht hat (dieses spezifische Problem wurde durch Umbenennung des Moduls in _socket nebenbei gelöst).
Signatur des Einstiegspunkts
Der Einstiegspunkt ist derzeit eine Prozedur (gibt void zurück). Dies weicht von den üblichen Aufrufkonventionen ab; Aufrufer können nur durch Überprüfung von PyErr_Occurred feststellen, ob während der Initialisierung ein Fehler aufgetreten ist. Der Einstiegspunkt sollte einen PyObject* zurückgeben, der das erstellte Modul sein wird, oder NULL im Falle einer Ausnahme.
Mehrere Interpreter
Derzeit teilen sich Erweiterungsmodule ihren Zustand über alle Interpreter hinweg. Dies ermöglicht unerwünschte Informationslecks zwischen Interpretern: Ein Skript könnte Objekte in einem Erweiterungsmodul dauerhaft beschädigen und möglicherweise alle Skripte in anderen Interpretern brechen.
Spezifikation
Die Initialisierungsroutinen für Module ändern ihre Signatur zu
PyObject *PyInit_<modulename>()
Die Initialisierungsroutine wird einmal pro Interpreter aufgerufen, wenn das Modul importiert wird. Sie sollte jedes Mal ein neues Modulobjekt zurückgeben.
Um modulspezifischen Zustand in C-Variablen zu speichern, enthält jedes Modulobjekt einen Speicherblock, der nur vom Modul interpretiert wird. Die für das Modul verwendete Speichermenge wird beim Erstellen des Moduls angegeben.
Zusätzlich zur Initialisierungsfunktion kann ein Modul eine Reihe zusätzlicher Callback-Funktionen implementieren, die aufgerufen werden, wenn die Funktionen tp_traverse, tp_clear und tp_free des Moduls aufgerufen werden und wenn das Modul neu geladen wird.
Die gesamte Moduldefinition wird in einer Struktur PyModuleDef zusammengefasst
struct PyModuleDef{
PyModuleDef_Base m_base; /* To be filled out by the interpreter */
Py_ssize_t m_size; /* Size of per-module data */
PyMethodDef *m_methods;
inquiry m_reload;
traverseproc m_traverse;
inquiry m_clear;
freefunc m_free;
};
Die Erstellung eines Moduls wird geändert, um einen optionalen PyModuleDef* zu erwarten. Der Modulstatus wird null-initialisiert.
Jede Modulmethode erhält das Modulobjekt als ersten Parameter übergeben. Um auf die Moduldaten zuzugreifen, wird eine Funktion
void* PyModule_GetState(PyObject*);
bereitgestellt. Zusätzlich wird, um ein Modul effizienter nachzuschlagen als über sys.modules zu gehen, eine Funktion
PyObject* PyState_FindModule(struct PyModuleDef*);
bereitgestellt. Diese Nachschlagefunktion verwendet einen Index im Feld m_base, um das Modul nach Index und nicht nach Namen zu finden.
Da alle Python-Objekte über die Python-Speicherverwaltung gesteuert werden sollen, wird die Verwendung von "statischen" Typobjekten nicht empfohlen, es sei denn, das Typobjekt selbst hat keinen speicherverwalteten Zustand. Um die Definition von Heap-Typen zu vereinfachen, wird eine neue Methode
PyTypeObject* PyType_Copy(PyTypeObject*);
hinzugefügt.
Beispiel
xxmodule.c würde geändert, um die Funktion initxx zu entfernen und stattdessen den folgenden Code hinzuzufügen
struct xxstate{
PyObject *ErrorObject;
PyObject *Xxo_Type;
};
#define xxstate(o) ((struct xxstate*)PyModule_GetState(o))
static int xx_traverse(PyObject *m, visitproc v,
void *arg)
{
Py_VISIT(xxstate(m)->ErrorObject);
Py_VISIT(xxstate(m)->Xxo_Type);
return 0;
}
static int xx_clear(PyObject *m)
{
Py_CLEAR(xxstate(m)->ErrorObject);
Py_CLEAR(xxstate(m)->Xxo_Type);
return 0;
}
static struct PyModuleDef xxmodule = {
{}, /* m_base */
sizeof(struct xxstate),
&xx_methods,
0, /* m_reload */
xx_traverse,
xx_clear,
0, /* m_free - not needed, since all is done in m_clear */
}
PyObject*
PyInit_xx()
{
PyObject *res = PyModule_New("xx", &xxmodule);
if (!res) return NULL;
xxstate(res)->ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
if (!xxstate(res)->ErrorObject) {
Py_DECREF(res);
return NULL;
}
xxstate(res)->XxoType = PyType_Copy(&Xxo_Type);
if (!xxstate(res)->Xxo_Type) {
Py_DECREF(res);
return NULL;
}
return res;
}
Diskussion
Tim Peters berichtet in [1] , dass PythonLabs eine solche Funktion zu einem früheren Zeitpunkt in Betracht gezogen hat, und listet die folgenden zusätzlichen Hooks auf, die derzeit in diesem PEP nicht unterstützt werden
- wenn das Modulobjekt aus sys.modules gelöscht wird
- wenn Py_Finalize aufgerufen wird
- wenn Python beendet wird
- wenn die Python DLL entladen wird (nur Windows)
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3121.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT