PEP 743 – Hinzufügen von Py_OMIT_LEGACY_API zur Python C API
- Autor:
- Victor Stinner <vstinner at python.org>, Petr Viktorin <encukou at gmail.com>
- PEP-Delegate:
- C API Working Group
- Discussions-To:
- Discourse thread
- Status:
- Entwurf
- Typ:
- Standards Track
- Erstellt:
- 11. März 2024
- Python-Version:
- 3.15
Zusammenfassung
Fügen Sie das C-Makro Py_OMIT_LEGACY_API hinzu, das veraltete und soft-veraltete Symbole ausblendet und es Benutzern ermöglicht, die Verwendung von APIs mit bekannten Problemen zu vermeiden, die andere APIs lösen.
Fügen Sie außerdem namensraumgestützte Alternativen für APIs ohne das Präfix Py_ hinzu und kennzeichnen Sie die ursprünglichen Namen als soft-veraltet.
Motivation
Ein Teil der Python C API hat Fehler, die erst im Nachhinein offensichtlich werden.
Wenn eine API das Hinzufügen von Funktionen oder Optimierungen verhindert oder ein ernsthaftes Sicherheitsrisiko oder eine Wartungsbelastung darstellt, können wir sie wie in PEP 387 beschrieben, als veraltet kennzeichnen und entfernen.
Dies hinterlässt uns jedoch mit einigen APIs, die „scharfe Kanten“ haben – sie funktionieren für ihre aktuellen Benutzer einwandfrei, sollten aber in neuem Code vermieden werden. Zum Beispiel
- APIs, die keine Ausnahmen signalisieren können, so dass Fehler entweder ignoriert werden oder der Prozess mit einem fatalen Fehler beendet wird. Zum Beispiel
PyObject_HasAttr. - APIs, die nicht threadsicher sind, z. B. durch Ausleihen von Referenzen aus veränderlichen Objekten oder durch Offenlegung unfertiger veränderlicher Objekte. Zum Beispiel
PyDict_GetItemWithError. - APIs mit Namen, die nicht das Präfix
Py/_Pyverwenden und daher mit anderem Code kollidieren können. Zum Beispiel:setter.
Es ist wichtig zu beachten, dass es trotz solcher Mängel normalerweise möglich ist, die API korrekt zu verwenden. In einer Single-Threaded-Umgebung ist die Threadsicherheit beispielsweise kein Problem. Wir wollen keinen funktionierenden Code brechen, selbst wenn er APIs verwendet, die in einigen – oder sogar den *meisten* – anderen Kontexten falsch wären.
Andererseits möchten wir Benutzer von solch „unerwünschten“ APIs in *neuem* Code ablenken, insbesondere wenn eine sicherere Alternative existiert.
Hinzufügen des Präfixes Py
Einige Namen, die in CPython-Headern definiert sind, sind nicht namensraumgestützt: Sie entbehren des Präfixes Py (oder einer Variante: _Py und alternative Groß-/Kleinschreibungen). Zum Beispiel deklarieren wir einen Funktionstyp namens einfach setter.
Obwohl solche Namen nicht in der ABI exportiert werden (wie von make smelly geprüft), können sie mit Benutzercode und, was wichtiger ist, mit Bibliotheken, die mit Drittanbietererweiterungen verknüpft sind, kollidieren.
Obwohl es möglich wäre, namensraumgestützte Aliase bereitzustellen und diese Namen als (soft-)veraltet zu kennzeichnen, ist der einzige Weg, sie nicht mit Drittanbietercode kollidieren zu lassen, sie gar nicht in den Python-Headern zu definieren.
Begründung
Wir möchten Benutzern eine einfache Möglichkeit bieten, „unerwünschte“ APIs zu vermeiden, wenn sie sich dafür entscheiden.
Es könnte ausreichen, dies Drittanbieter-Lintern zu überlassen. Dazu bräuchten wir eine gute Möglichkeit, eine Liste von (soft-)veralteten APIs für solche Linter verfügbar zu machen. Während wir das tun, können wir – ziemlich einfach – die Arbeit des Linter direkt in den CPython-Headern erledigen und so die Notwendigkeit eines zusätzlichen Tools vermeiden. Im Gegensatz zu Python ist es in C ziemlich einfach, die verfügbaren APIs zu begrenzen – für ein ganzes Projekt oder für jede einzelne Quelldatei –, indem Benutzer ein „Opt-in“-Makro definieren.
Wir tun bereits etwas Ähnliches mit Py_LIMITED_API, das die verfügbaren APIs auf eine Untermenge beschränkt, die zu einer stabilen ABI kompiliert wird. (Im Nachhinein hätten wir für diese Art der Einschränkung einen anderen Makronamen verwenden sollen, aber es ist zu spät, das jetzt zu ändern.)
Um es klar auszudrücken, dieser Mechanismus ist *kein* Ersatz für die Veraltung. Veraltung ist für APIs gedacht, die neue Funktionen oder Optimierungen verhindern oder ein Sicherheitsrisiko oder eine Wartungsbelastung darstellen. Dieser Mechanismus hingegen ist für Fälle gedacht, in denen „wir einen etwas besseren Weg gefunden haben, Dinge zu tun“ – vielleicht einen, der schwerer zu missbrauchen ist, oder einfach einen weniger irreführenden Namen hat. (Leicht gesagt: Viele Leute konfigurieren einen Code-Qualitätsprüfer so, dass er sie wegen der Anzahl leerer Zeilen zwischen Funktionen anschreit. Helfen wir ihnen, substanziellere „Code-Smells“ zu identifizieren!)
Das vorgeschlagene Makro *ändert* keine API-Definitionen; es *blendet* sie nur aus. Wenn also Code mit dem Makro kompiliert, kompiliert er auch ohne es mit identischem Verhalten. Dies hat Auswirkungen für Core-Entwickler: Um unerwünschtes Verhalten zu behandeln, müssen wir neue, bessere APIs einführen und *dann* die alten entmutigen. Dies impliziert wiederum, dass wir eine einzelne API betrachten und alle ihre bekannten Probleme auf einmal beheben sollten, anstatt codebasenweite Suchen nach einer einzelnen Art von Problem durchzuführen, damit wir mehrere Umbenennungen derselben Funktion vermeiden.
Hinzufügen des Präfixes Py
Ein Opt-in-Makro ermöglicht es uns, Definitionen wegzulassen, die mit Drittanbieterbibliotheken kollidieren könnten.
Spezifikation
Wir führen das Makro Py_OMIT_LEGACY_API ein. Wenn dieses Makro vor #include <Python.h> definiert wird, werden einige API-Definitionen – wie unten beschrieben – aus den Python-Header-Dateien weggelassen.
Das Makro lässt nur vollständige Top-Level-Definitionen aus, die aus <Python.h> exportiert werden. Andere Dinge (die ABI, Strukturdefinitionen, Makroerweiterungen, statische Inline-Funktionskörper usw.) sind nicht betroffen.
Die C API Working Group (PEP 731) hat die Autorität über die Menge der weggelassenen Definitionen.
Die Menge der weggelassenen Definitionen wird an eine bestimmte Feature-Version von CPython gebunden und in jeder 3.x.0 Beta 1-Version finalisiert. In seltenen Fällen können Einträge jederzeit entfernt werden (d. h. zur Verwendung verfügbar gemacht werden).
Anforderungen an weggelassene APIs
Eine API, die mit Py_OMIT_LEGACY_API weggelassen wird, muss
- soft-veraltet sein (siehe PEP 387);
- für alle bekannten Anwendungsfälle der API eine dokumentierte Alternative oder einen Workaround haben;
- Tests haben, um sicherzustellen, dass sie weiterhin funktioniert (außer bei 1:1-Umbenennungen mit
#defineodertypedef); - dokumentiert sein (es sei denn, sie wurde in früheren Versionen der Dokumentation noch nie erwähnt); und
- von der C API Working Group genehmigt sein. (Die WG kann Pauschalgenehmigungen für Gruppen verwandter APIs erteilen; siehe Anfangsmenge unten für Beispiele.)
Beachten Sie, dass Py_OMIT_LEGACY_API für APIs gedacht ist, die trivial durch eine bessere Alternative ersetzt werden können. APIs ohne Ersatz sollten im Allgemeinen stattdessen veraltet werden.
Speicherort
Alle von Py_OMIT_LEGACY_API weggelassenen API-Definitionen werden in eine neue Header-Datei verschoben: Include/legacy.h.
Dies soll Autoren von Linter helfen, Listen zu kompilieren, damit sie die API mit Warnungen statt Fehlern markieren können.
Beachten Sie, dass für einfache Umbenennungen von nur Quellcode-Konstrukten (Makros, Typen) erwartet wird, dass Namen in derselben Version – oder demselben PR – weggelassen werden, die eine Ersetzung hinzufügt. Das bedeutet, dass die ursprüngliche Definition umbenannt und ein typedef oder #define für den alten Namen zu Include/legacy.h hinzugefügt wird.
Dokumentation
Die Dokumentation für weggelassene APIs sollte im Allgemeinen
- nach der empfohlenen Ersetzung erscheinen,
- auf die Ersetzung verweisen (z. B. „Ähnlich wie X, aber…“), und
- sich auf Unterschiede zur Ersetzung und Migrationshinweise konzentrieren.
Ausnahmen sind möglich, wenn es dafür gute Gründe gibt.
Anfangsmenge
Die folgenden APIs werden mit Py_OMIT_LEGACY_API weggelassen
- Ausleihen von Referenzen zurückgebende APIs weglassen
Weggelassene API Ersetzung PyDict_GetItem()PyDict_GetItemRef()PyDict_GetItemString()PyDict_GetItemStringRef()PyImport_AddModule()PyImport_AddModuleRef()PyList_GetItem()PyList_GetItemRef() - Veraltete APIs weglassen
Weggelassene veraltete API Ersetzung PY_FORMAT_SIZE_T"z"PY_UNICODE_TYPEwchar_tPyCode_GetFirstFree()PyUnstable_Code_GetFirstFree()PyCode_New()PyUnstable_Code_New()PyCode_NewWithPosOnlyArgs()PyUnstable_Code_NewWithPosOnlyArgs()PyImport_ImportModuleNoBlock()PyImport_ImportModule()PyMem_DEL()PyMem_Free()PyMem_Del()PyMem_Free()PyMem_FREE()PyMem_Free()PyMem_MALLOC()PyMem_Malloc()PyMem_NEW()PyMem_New()PyMem_REALLOC()PyMem_Realloc()PyMem_RESIZE()PyMem_Resize()PyModule_GetFilename()PyModule_GetFilenameObject()PyOS_AfterFork()PyOS_AfterFork_Child()PyObject_DEL()PyObject_Free()PyObject_Del()PyObject_Free()PyObject_FREE()PyObject_Free()PyObject_MALLOC()PyObject_Malloc()PyObject_REALLOC()PyObject_Realloc()PySlice_GetIndicesEx()(zwei Aufrufe; siehe aktuelle Dokumentation) PyThread_ReInitTLS()(nicht mehr benötigt) PyThread_create_key()PyThread_tss_alloc()PyThread_delete_key()PyThread_tss_free()PyThread_delete_key_value()PyThread_tss_delete()PyThread_get_key_value()PyThread_tss_get()PyThread_set_key_value()PyThread_tss_set()PyUnicode_AsDecodedObject()PyUnicode_Decode()PyUnicode_AsDecodedUnicode()PyUnicode_Decode()PyUnicode_AsEncodedObject()PyUnicode_AsEncodedString()PyUnicode_AsEncodedUnicode()PyUnicode_AsEncodedString()PyUnicode_IS_READY()(nicht mehr benötigt) PyUnicode_READY()(nicht mehr benötigt) PyWeakref_GET_OBJECT()PyWeakref_GetRef()PyWeakref_GetObject()PyWeakref_GetRef()Py_UNICODEwchar_t_PyCode_GetExtra()PyUnstable_Code_GetExtra()_PyCode_SetExtra()PyUnstable_Code_SetExtra()_PyDict_GetItemStringWithError()PyDict_GetItemStringRef()_PyEval_RequestCodeExtraIndex()PyUnstable_Eval_RequestCodeExtraIndex()_PyHASH_BITSPyHASH_BITS_PyHASH_IMAGPyHASH_IMAG_PyHASH_INFPyHASH_INF_PyHASH_MODULUSPyHASH_MODULUS_PyHASH_MULTIPLIERPyHASH_MULTIPLIER_PyObject_EXTRA_INIT(nicht mehr benötigt) _PyThreadState_UncheckedGet()PyThreadState_GetUnchecked()_PyUnicode_AsString()PyUnicode_AsUTF8()_Py_HashPointer()Py_HashPointer()_Py_T_OBJECTPy_T_OBJECT_EX_Py_WRITE_RESTRICTED(nicht mehr benötigt) - Soft-veraltet und APIs weglassen
Weggelassene veraltete API Ersetzung PyDict_GetItemWithError()PyDict_GetItemRef()PyDict_SetDefault()PyDict_SetDefaultRef()PyMapping_HasKey()PyMapping_HasKeyWithError()PyMapping_HasKeyString()PyMapping_HasKeyStringWithError()PyObject_HasAttr()PyObject_HasAttrWithError()PyObject_HasAttrString()PyObject_HasAttrStringWithError() - Legacy-API von
<structmember.h>weglassenDie Header-Datei
structmember.h, die nicht von<Python.h>eingeschlossen wird und separat eingeschlossen werden muss, wird#errorauslösen, wennPy_OMIT_LEGACY_APIdefiniert ist. Dies betrifft die folgenden APIsWeggelassene veraltete API Ersetzung T_SHORTPy_T_SHORTT_INTPy_T_INTT_LONGPy_T_LONGT_FLOATPy_T_FLOATT_DOUBLEPy_T_DOUBLET_STRINGPy_T_STRINGT_OBJECT( tp_getset; Dokumentation wird noch geschrieben)T_CHARPy_T_CHART_BYTEPy_T_BYTET_UBYTEPy_T_UBYTET_USHORTPy_T_USHORTT_UINTPy_T_UINTT_ULONGPy_T_ULONGT_STRING_INPLACEPy_T_STRING_INPLACET_BOOLPy_T_BOOLT_OBJECT_EXPy_T_OBJECT_EXT_LONGLONGPy_T_LONGLONGT_ULONGLONGPy_T_ULONGLONGT_PYSSIZETPy_T_PYSSIZETT_NONE( tp_getset; Dokumentation wird noch geschrieben)READONLYPy_READONLYPY_AUDIT_READPy_AUDIT_READREAD_RESTRICTEDPy_AUDIT_READPY_WRITE_RESTRICTED(nicht mehr benötigt) RESTRICTEDPy_AUDIT_READ - Soft-veraltete Makros weglassen
Weggelassene Makros Ersetzung Py_IS_NAN()isnan()(C99+<math.h>)Py_IS_INFINITY()isinf(X)(C99+<math.h>)Py_IS_FINITE()isfinite(X)(C99+<math.h>)Py_MEMCPY()memcpy()(C<string.h>) - Soft-veraltet und Typedefs ohne das Präfix
Py/_Pyweglassen (getter,setter,allocfunc, …), zugunsten *neuer* mit Präfix (Py_getter, etc.) - Soft-veraltet und Makros ohne das Präfix
Py/_Pyweglassen (METH_O,CO_COROUTINE,FUTURE_ANNOTATIONS,WAIT_LOCK, …), zugunsten *neuer* mit Präfix (Py_METH_O, etc.). - Alle anderen, von der C API Workgroup genehmigten
Wenn einige dieser vorgeschlagenen Ersetzungen oder zugehörige Dokumentationen nicht rechtzeitig für 3.14.0b1 hinzugefügt werden, werden sie mit späteren Versionen von Py_OMIT_LEGACY_API weggelassen. (Wir erwarten dies für Makros, die von configure generiert werden: HAVE_*, WITH_*, ALIGNOF_*, SIZEOF_* und einige ohne gemeinsames Präfix.)
Implementierung
TBD
Abwärtskompatibilität
Das Makro ist abwärtskompatibel. Entwickler können das Makro in ihrem eigenen Tempo einführen und aktualisieren, möglicherweise dateiweise.
Zukünftige Versionen von CPython können der Menge der von Py_OMIT_LEGACY_API ausgeblendeten APIs weitere hinzufügen, was Benutzercode brechen kann. Die Lösung besteht darin, das Makro zu undefinieren (was sicher ist) oder den Code zu überarbeiten.
Diskussionen
- PEP 743 – Hinzufügen von Py_COMPAT_API_VERSION zur Python C API (Take 2) (Juli 2024)
- Abschluss der großen Umbenennung (Mai 2024)
- PEP 743: Hinzufügen von Py_COMPAT_API_VERSION zur Python C API (März 2024)
- C API Evolutions: Makro zum Ausblenden veralteter Funktionen (Oktober 2023)
- C API Probleme: Opt-in Makro für eine neue saubere API? Teilmenge von Funktionen ohne bekannte Probleme (Juni 2023)
Vorhandene Lösungen
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-0743.rst
Zuletzt geändert: 2025-09-30 12:20:21 GMT