PEP 809 – Stabile ABI für die Zukunft
- Autor:
- Steve Dower <steve.dower at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Entwurf
- Typ:
- Standards Track
- Benötigt:
- 703, 793, 697
- Erstellt:
- 19. Sep. 2025
- Python-Version:
- 3.15
- Post-History:
- 30. Sep. 2025
Zusammenfassung
Die stabile ABI als abi3 kann nicht mehr beibehalten werden und erfordert einen Ersatz. abi2026 wird der erste Ersatz sein, der die Auflösung aktueller bekannter Inkompatibilitäten bietet, mit geplanter Außerbetriebnahme nach mindestens 10 Jahren. Die nächste ABI (z. B. abi2031) wird mindestens fünf Jahre lang mit der vorhergehenden überlappen.
Langfristige Stabilität wird durch einen Mechanismus zur Laufzeit-ABI-Erkennung ermöglicht, der es Erweiterungen ermöglicht, mit früheren Versionen ausgeführt zu werden, die dieselbe ABI unterstützen. Änderungen und Ergänzungen während der Lebensdauer einer ABI können als Interfaces hinzugefügt werden, sodass sie zur Laufzeit erkannt werden können, damit Aufrufer geeignetes Fallback-Verhalten wählen können. Derzeit verhindern solche Ergänzungen, dass Erweiterungen auf früheren Laufzeitumgebungen überhaupt geladen werden.
Die abi3 ABI wird in GIL-aktivierten Builds für mindestens fünf Jahre beibehalten, danach kann sie außer Betrieb genommen werden (nur abi2026 und neuere sind verfügbar). Es ist möglich, dass GIL-aktivierte Builds vorher vollständig außer Betrieb genommen werden. Frei-Thread-Builds verfügen nicht über abi3, daher wäre ihre erste stabile ABI abi2026.
Terminologie
Diese PEP verwendet "GIL-aktivierter Build" als Antonym zu "Frei-Thread-Build", d. h. ein Interpreter oder eine Erweiterung, die ohne Py_GIL_DISABLED erstellt wurde.
Motivation
Die stabile ABI ist derzeit nicht für Frei-Thread-Builds verfügbar. Erweiterungen schlagen fehl, wenn Py_LIMITED_API definiert ist. Ebenso schlagen Erweiterungen, die für GIL-aktivierte Builds von CPython erstellt wurden, auf Frei-Thread-Builds fehl (oder stürzen ab).
In seinem Akzeptanz-Post für PEP 779 erklärte der Steering Council, dass er "erwartet, dass die stabile ABI für Free-Threading für Python 3.15 vorbereitet und definiert wird".
Diese PEP schlägt eine stabile ABI vor, die mit allen Varianten von 3.15 und neuer kompatibel ist und es Paketentwicklern ermöglicht, eine einzige Version ihrer Erweiterungen zu erstellen.
Hintergrund
Pythons stabile ABI, wie definiert in PEP 384 und PEP 652, bietet eine Möglichkeit, Erweiterungsmodule zu kompilieren, die auf mehreren Nebenversionen des CPython-Interpreters geladen werden können. Mehrere Projekte nutzen dies, um die Anzahl der zu bauenden und zu verteilenden Wheels (binäre Artefakte) für jede Veröffentlichung zu begrenzen und/oder um das Testen mit Vorabversionen von Python zu erleichtern.
Da frei-threaded Builds (PEP 703) voraussichtlich der Standard werden (PEP 779), benötigen wir eine Möglichkeit, die stabile ABI für diese Builds verfügbar zu machen.
Um gegen die stabile ABI zu bauen, muss die Erweiterung eine Limited API verwenden, d. h. nur eine Teilmenge der Funktionen, Strukturen usw. von CPython. Die Limited API ist versioniert, und der Aufbau gegen Limited API 3.X ergibt eine Erweiterung, die ABI-kompatibel mit CPython 3.X und jeder späteren Version ist (obwohl Fehler in CPython manchmal zu Inkompatibilitäten in der Praxis führen). Außerdem ist die Limited API nicht "stabil": neuere Versionen können API-Elemente entfernen, die in älteren Versionen verfügbar waren.
Diese PEP schlägt eine signifikante Änderung der Versionierung sowohl der Limited API als auch der stabilen ABI vor. Ziel ist es, eine langfristige Verwaltung von Stabilität und Kompatibilität zu ermöglichen und gleichzeitig den Nutzern der begrenzten Teilmengen den Zugang zu Innovationen in späteren Python-Versionen zu ermöglichen.
Begründung
Das Design dieser PEP macht mehrere Annahmen
- Eine ABI
- Ein einzelnes kompiliertes Erweiterungsmodul sollte sowohl frei-threaded als auch GIL-aktivierte Builds unterstützen.
- Keine Rückwärtskompatibilität
- Die neue Limited API wird von CPython 3.14 und älter nicht unterstützt. Projekte, die diese Unterstützung benötigen, können separate Erweiterungen speziell für den frei-threaded Interpreter 3.14 und für ältere stabile ABI-Versionen erstellen.
- API-Änderungen sind in Ordnung
- Die neue Limited API erfordert möglicherweise erhebliche Codeänderungen von Erweiterungsautoren. Projekte, die dies (noch) nicht können, können weiterhin die Limited API 3.14 verwenden, was Erweiterungen ergibt, die nur mit GIL-aktivierten Builds kompatibel sind.
- Keine zusätzliche Konfiguration
- Wir führen keine neuen "Schalter" ein, die beeinflussen, welche API verfügbar ist und mit welcher ABI Kompatibilität besteht.
Spezifikation
Beachten Sie, dass ein Großteil der Spezifikation identisch mit der von PEP 803 ist, und die Leser sollten sich für Details an diesen Vorschlag wenden. Die Abschnitte ABI-Stabilität, Build-Zeit-Makros und Interfaces API sind einzigartig für diesen Vorschlag.
ABI-Stabilität
Die stabile ABI wird für mindestens 10 Jahre eingefroren. Wenn eine neue Version der stabilen ABI eingefroren wird, wird die bestehende Version noch mindestens 5 Jahre lang unterstützt. Dies ermöglicht eine ausreichende Migrationszeit für Paketverantwortliche (und andere Benutzer), um ihre gesamte Palette unterstützter Versionen gleichzeitig zu migrieren. Wenn das Python-Kernentwicklungsteam jedoch keinen Grund sieht, die aktuelle stabile ABI zu ersetzen, kann das Einfrieren einer neuen Version verschoben werden.
Neue stabile ABIs werden über den PEP-Prozess definiert, wobei ihr Name das Erscheinungsjahr der ersten Laufzeitumgebung widerspiegelt, die sie unterstützt.
Wenn eine stabile ABI eingefroren wird, wird das Jahr zum Namen der ABI. Wir erwarten beispielsweise, dass die erste ABI unter diesem Schema abi2026 sein wird und bis mindestens 2036 von allen Versionen unterstützt wird. Wenn die Unterstützung im Jahr 2036 eingestellt wird, wäre abi2031 das Migrationsziel, was Paketentwicklern ermöglicht, mindestens fünf Jahre nach ihrer eigenen Migration abgedeckte Versionen zu unterstützen.
Solange sie eingefroren ist, sind keinerlei ABI-Änderungen zulässig. Ergänzungen sind ebenso wenig zulässig wie Entfernungen, Modifikationen oder drastische semantische Änderungen. Entscheidend ist, dass ein gegen eine bestimmte ABI kompiliertes Erweiterungsmodul erfolgreich (d. h. alle importierten Symbole sind auf allen unterstützten Plattformen erfüllt) gegen jede Python-Version geladen werden muss, die diese ABI unterstützt, sei es früher oder später.
Semantische Änderungen, die zur Laufzeit nicht über eine bestehende kompatible ABI erkannt werden können, sind nicht zulässig. Das heißt, die APIs zum Erkennen, ob ein bestimmtes Verhalten bei der aktuellen Python-Version zu erwarten ist, müssen auf allen früheren Versionen verfügbar gewesen sein, die die ABI unterstützen.
Opaque PyObject
Version 3.15 der Limited API wird eine Reihe von Strukturen undurchsichtig machen, sodass Nutzer von ihnen keine Annahmen über ihre Größe oder ihr Layout treffen können. Die Details finden sich in PEP 803, und der Vorschlag hier ist identisch.
Neuer Export-Hook (PEP 793)
Die Implementierung dieser PEP erfordert die Annahme von PEP 793 (PyModExport: Ein neuer Einstiegspunkt für C-Erweiterungsmodule), die einen neuen "Export-Hook" zur Definition von Erweiterungsmodulen bereitstellt. Die Verwendung des neuen Hooks wird in Limited API 3.15 zwingend erforderlich.
Dieser Vorschlag ist identisch mit dem von PEP 803.
Laufzeit-ABI-Prüfungen
Siehe PEP 803 für Details. Dieser Vorschlag ist identisch.
Build-Zeit-Makros
Wir fordern, dass Py_LIMITED_API auf 0x03ff_YYYY gesetzt wird – d. h. das obere Wort ist eine Konstante 0x03ff, während das untere Wort der ABI-Name (Jahr) als Hexadezimalwert ist. Obwohl dies zu einem Dezimalwert führt, der nicht mit dem Jahr übereinstimmt, halten wir dies für unwichtig, da der Wert ein beliebiger Bezeichner ist und eher als Konstante (in einer cc-Kommandozeile) als ein berechneter Wert angegeben wird.
Die Verwendung von 0x03ff als Konstante dient der Kompatibilität mit früheren Laufzeitumgebungen. Dieselbe Konstante wählt bei Headern, die nur abi3 unterstützen, die "vollständigste" Version von ABI3 in dieser Version aus. Zum Beispiel wählt 0x03ff2026 in 3.15+ abi2026 aus, während es in 3.10 die Version von ABI3 wählt, die für 3.10-3.14 funktioniert.
Neue API
Die Implementierung dieser PEP ermöglicht es, Erweiterungen zu erstellen, die auf frei-threaded Python erfolgreich geladen werden können, aber nicht unbedingt Thread-sicher ohne GIL sind.
Die Limited API, um Thread-Sicherheit ohne GIL zu ermöglichen – vermutlich PyMutex, PyCriticalSection und Ähnliches – wird über die C-API-Arbeitsgruppe oder in einer Folge-PEP hinzugefügt.
Interfaces API
Eine neue Interfaces API wird zu Python und der neuen Limited API hinzugefügt. Diese API dient dazu, die Anforderung "semantische Änderungen sind auf allen Versionen erkennbar" aus dem Abschnitt ABI-Stabilität oben zu erfüllen. Das heißt, Konsumenten [1] können eine neue API sofort nutzen, für die Limited API mit der neuesten Version kompilieren und die binäre Kompatibilität für alle Versionen beibehalten, die diese ABI unterstützen.
Kurz gesagt, die primäre API ist PyObject_GetInterface(), die an einen neuen, nur nativen Typ-Slot delegiert, um eine C-Struktur mit entweder Daten oder Funktionszeigern zu füllen. Da die C-Strukturdefinition in die Erweiterung eingebettet ist und nicht zur Laufzeit abgerufen wird, kann ein Erweiterungsmodul spätere Strukturen erkennen, während es gegen Versionen von Python ausgeführt wird, die diese nicht bereitstellen.
Wenn der Aufruf von PyObject_GetInterface eine Struktur anfordert, die in der aktuellen Version nicht verfügbar ist oder für das bereitgestellte Objekt nicht verfügbar ist, schlägt der Aufruf sicher fehl. Der Aufrufer kann dann Fallback-Logik verwenden (z. B. abstrakte Python-APIs verwenden) oder abbrechen, je nach Präferenz.
Wenn beispielsweise während der Lebensdauer von abi2026 eine neue API hinzugefügt würde, die einen effizienteren Zugriff auf die internen Daten eines int-Objekts ermöglicht, würden wir anstatt einer neuen API ein neues Interface erstellen: eine Struktur, die einen Funktionszeiger zum Kopieren der Daten an einen neuen Speicherort enthält, und einen zuvor ungenutzten Index/Namen für dieses Interface. Der Aufrufer kann zuerst PyObject_GetInterface(int_object, &intf_struct) aufrufen; wenn dies erfolgreich ist, kann er (hypothetisch) (*intf_struct.copy_bits)(&intf_struct, dest, sizeof(dest)) aufrufen; wenn es fehlschlägt, kann er PyObject_CallMethod(int_object, "to_bytes", ...) verwenden, um denselben Vorgang durchzuführen, aber weniger effizient. Das Endergebnis dieses Beispiels ist ein einzelnes Erweiterungsmodul, das binärkompatibel mit *allen* Versionen ist, die abi2026 unterstützen, aber effizienter ist, wenn es gegen neuere Python-Versionen ausgeführt wird.
Übersicht abgeschlossen, hier ist die vollständige Spezifikation jeder neuen API
// Abstract API to request an interface for an object (or type).
PyAPI_FUNC(int) PyObject_GetInterface(PyObject *obj, void *intf);
// API to release an interface.
PyAPI_FUNC(int) PyInterface_Release(void *intf);
// Expected layout of the start of each interface. Actual interface structs
// will add additional function pointers or data.
typedef struct PyInterface_Base {
// sizeof(self), for additional validation that the caller is passing
// the correct structure.
Py_ssize_t size;
// Unique identifier for the struct. Details below.
uint64_t name;
// Function to release the struct (e.g. to decref any PyObject fields).
// Should only be invoked by PyInterface_Release(), not directly.
int (*release)(struct PyInterface_Base *intf);
} PyInterface_Base;
// Type slot definition for PyTypeObject field.
typedef int (*Py_getinterfacefunc)(PyObject *o, PyInterface_Base *intf);
Die eindeutige Kennung für die Struktur ist eine 64-Bit-Ganzzahl, die als Makro definiert ist (um sicherzustellen, dass kompilierte Erweiterungsmodule den Wert einbetten und nicht versuchen, ihn zur Laufzeit zu ermitteln). Die oberen 32 Bit sind der Namensraum, und Implementierer, die ihre eigenen Strukturen definieren, sollten einen eindeutigen Wert für sich selbst wählen. Null ist für CPython reserviert.
Der Schnittstellenname dient zur Identifizierung des Strukturlayouts, und so kann jedes definierte Objekt einen Schnittstellennamen aus einem anderen Namensraum wiederverwenden, vorausgesetzt, die Struktur stimmt überein. Dies ist beabsichtigt, da es Drittanbietertypen ermöglicht, dieselben Schnittstellen wie Kern-Typen zu implementieren, ohne auf die gemeinsame Nutzung der Implementierung angewiesen zu sein. Um klarzustellen, kann eine für CPython definierte Schnittstelle von anderen Erweiterungsmodulen verwendet werden, ohne den Namen oder den Namensraum des Namens zu ändern.
Betrachten Sie beispielsweise eine hypothetische Schnittstelle zur Implementierung von PyDict_GetItemString(). Der Kern-dict-Typ kann interne Optimierungen vornehmen, um Einträge nach Schlüssel zu finden, während ein externer Typ dieselbe Schnittstelle verwenden kann, um seine eigenen Optimierungen vorzunehmen. Für den Aufrufer sieht es so aus, als würde er dieselbe Schnittstelle verwenden, und somit ist der Aufrufer mit einer größeren Bandbreite von Typen kompatibel, als wenn er (z. B.) die konkreten Objekt-APIs von CPython verwenden würde.
Schnittstellennamen können jederzeit aus Headern entfernt werden, und Strukturdefinitionen können nur entfernt werden, wenn alle stabilen ABI-Versionen, die sie unterstützen, vollständig außer Betrieb genommen sind. Objekte können jedoch die Rückgabe einer bestimmten Schnittstelle einstellen, wenn diese nicht mehr empfohlen oder zuverlässig ist, auch wenn frühere Versionen sie zurückgegeben haben. Laufzeit-Deprecationswarnungen können bei Bedarf verwendet werden, es ist keine spezifische Regel festgelegt.
Schnittstellenstrukturen sind fest und können nicht geändert werden. Wenn eine Änderung erforderlich ist, sollte eine neue Schnittstelle mit einem neuen Namen definiert werden. Die zu einer Struktur für eine Schnittstelle hinzugefügten Felder sind öffentliche APIs und sollten dokumentiert werden. Felder, die nicht zur direkten Verwendung bestimmt sind, sollten mit einem Unterstrich beginnen, können aber ansonsten nicht "privat" gemacht werden. Schnittstellen können eine Mischung aus Daten und Funktionszeigern bereitstellen oder starke PyObject *-Referenzen verwenden, um Wettlaufsituationen zu vermeiden.
Nach dem Abrufen einer Schnittstelle muss die Schnittstelle bis zur Freigabe gültig bleiben, auch wenn die Referenz auf das Objekt freigegeben wird. Das Verhalten der Schnittstelle kann Änderungen am zugrunde liegenden Objekt entsprechend handhaben, sollte seine Entscheidungen aber dokumentieren. Es wäre nicht unzumutbar, zwei ähnliche Schnittstellen zu haben, die diese Art von Änderungen unterschiedlich handhaben (z. B. eine Schnittstelle, die das Objekt für die Lebensdauer der Schnittstelle sperrt, während eine andere dies nicht tut).
Der Prozess der Hinzufügung neuer Limited APIs ändert sich etwas: Anstatt einer ABI, die mit jeder Veröffentlichung wächst, können neue APIs als echte Funktion hinzugefügt werden, wenn die Limited API nicht verwendet wird, sollten aber als statische Inline-Funktion für die Limited API hinzugefügt werden. Diese statische Inline-Funktion sollte eine Schnittstelle verwenden, um die Funktionalität zur Laufzeit zu erkennen, und einen abstrakten Fallback oder eine geeignete Ausnahme enthalten.
Das bedeutet, dass Konsumenten eine neue API sofort nutzen, für die Limited API mit der neuesten Version kompilieren und die binäre Kompatibilität für alle Versionen beibehalten können, die dieselbe stabile ABI unterstützen.
Beim nächsten Einfrieren der stabilen ABI kann die API entweder als echte Funktion in die neue stabile ABI/Limited API übernommen oder als Schnittstelle beibehalten werden.
Abwärtskompatibilität
Die Limited API 3.15 wird aufgrund entfernter Strukturen und Funktionen nicht abwärtskompatibel mit älteren CPython-Versionen sein.
Erweiterungsautoren, die nicht wechseln können, können weiterhin Limited API 3.14 und älter für die Verwendung auf GIL-aktivierten Builds nutzen.
Es werden keine Änderungen an abi3 für den GIL-aktivierten Build vorgenommen, und alle vorhandenen Symbole bleiben verfügbar, auch wenn diese unter neuen stabilen ABIs nicht mehr verfügbar sind.
Die Umstellung von frei-threaded Builds zum Standard/einzigen Release für CPython ist eine abwärtsinkompatible Änderung, und Erweiterungsautoren müssen migriert haben.
Sicherheitsimplikationen
Keine bekannt.
Wie man das lehrt
Die native ABI von Python kann als periodisch aktualisierter Standard oder Spezifikation beschrieben werden, identifiziert durch das Jahr, ähnlich wie bei anderen Sprachen. Jedes Erweiterungsmodul kann diese ABI verwenden und deklariert, welche ABI es als Teil seiner Distributionsinformationen erwartet. Jede Python-Implementierung kann wählen, eine bestimmte ABI-Version zu unterstützen, und jede Erweiterung, die diese Version ebenfalls unterstützt, sollte nutzbar sein.
Die Migration von abi3 zu einer neuen ABI kann Quellcodeänderungen mit sich bringen, kann aber als einmalige Aufgabe behandelt werden. In vielen, wenn nicht den meisten Fällen ist der Quellcode sowohl mit abi3 als auch mit der neuen ABI kompatibel, was die Produktion von Builds für alte und aktuelle Versionen vereinfacht. Im Allgemeinen sollten abi3-Builds mit der ältesten unterstützten CPython-Laufzeitumgebung und neue ABI-Builds mit der neuesten CPython-Laufzeitumgebung (oder einer anderen kompatiblen Laufzeitumgebung) erstellt werden.
Die Migration von einer ABI (z. B. abi2026) zur nächsten (z. B. abi2031) sollte eine manuelle Aufgabe sein. Es gibt genügend Überschneidungen zwischen ABI-Updates, sodass die meisten Projekte nur eines gleichzeitig unterstützen müssen und alle ihre Builds gleichzeitig aktualisieren können, wenn ihre eigene Support-Matrix dies zulässt. Es wird nicht erwartet, dass Paketverantwortliche sofort jede neue ABI unterstützen.
Vorwärts- und Rückwärtskompatibilität wird durch dynamische Schnittstellenerkennung gewährleistet. Code, der kürzlich hinzugefügte Limited API-Funktionen verwendet, läuft auch auf älteren Versionen, wenn auch potenziell mit geringerer Leistung. Siehe die Dokumentation für neue Funktionen, um Informationen über Limited API-spezifische Nuancen zu finden.
Nicht-C-Aufrufer sollten den Schnittstellenmechanismus direkt verwenden, um Zugriff auf neue Funktionen zu erhalten, ohne ihre Kompatibilität künstlich auf neuere Versionen zu beschränken. Die Namen und Strukturlayouts von Schnittstellen sind für immer garantiert stabil, obwohl nicht davon ausgegangen werden sollte, dass eine Schnittstelle für immer verfügbar sein wird, und geeigneter Fallback-Code (entweder eine alternative Implementierung oder Fehlerbehandlung) sollte enthalten sein.
Referenzimplementierung
Siehe PEP 803 für Links zu Referenzimplementierungen für die Aspekte, die von dieser PEP übernommen wurden.
Die Referenzimplementierung der Schnittstellen ist zooba/cpython#44.
Abgelehnte Ideen
[Siehe vorerst die Diskussion.]
Offene Fragen
[Siehe vorerst die Diskussion.]
Fußnoten
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-0809.rst
Zuletzt geändert: 2025-10-01 17:55:07 GMT