Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 733 – Eine Evaluierung der öffentlichen C-API von Python

Autor:
Erlend Egeberg Aasland <erlend at python.org>, Domenico Andreoli <domenico.andreoli at linux.com>, Stefan Behnel <stefan_ml at behnel.de>, Carl Friedrich Bolz-Tereick <cfbolz at gmx.de>, Simon Cross <hodgestar at gmail.com>, Steve Dower <steve.dower at python.org>, Tim Felgentreff <tim.felgentreff at oracle.com>, David Hewitt <1939362+davidhewitt at users.noreply.github.com>, Shantanu Jain <hauntsaninja at gmail.com>, Wenzel Jakob <wenzel.jakob at epfl.ch>, Irit Katriel <irit at python.org>, Marc-Andre Lemburg <mal at lemburg.com>, Donghee Na <donghee.na at python.org>, Karl Nelson <nelson85 at llnl.gov>, Ronald Oussoren <ronaldoussoren at mac.com>, Antoine Pitrou <solipsis at pitrou.net>, Neil Schemenauer <nas at arctrix.com>, Mark Shannon <mark at hotpy.org>, Stepan Sindelar <stepan.sindelar at oracle.com>, Gregory P. Smith <greg at krypto.org>, Eric Snow <ericsnowcurrently at gmail.com>, Victor Stinner <vstinner at python.org>, Guido van Rossum <guido at python.org>, Petr Viktorin <encukou at gmail.com>, Carol Willing <willingc at gmail.com>, William Woodruff <william at yossarian.net>, David Woods <dw-git at d-woods.co.uk>, Jelle Zijlstra <jelle.zijlstra at gmail.com>
Status:
Final
Typ:
Informational
Erstellt:
16-Oct-2023
Post-History:
01-Nov-2023

Inhaltsverzeichnis

Zusammenfassung

Dieser informelle PEP beschreibt unsere gemeinsame Sichtweise auf die öffentliche C-API. Das Dokument definiert

  • Zwecke der C-API
  • Stakeholder und ihre spezifischen Anwendungsfälle und Anforderungen
  • Stärken der C-API
  • Probleme der C-API, kategorisiert in neun Schwächebereiche

Dieses Dokument schlägt keine Lösungen für die identifizierten Probleme vor. Durch die Erstellung einer gemeinsamen Liste von C-API-Problemen wird dieses Dokument dazu beitragen, die fortlaufende Diskussion über Änderungsvorschläge zu leiten und Bewertungskriterien zu identifizieren.

Einleitung

Pythons C-API wurde nicht für die unterschiedlichen Zwecke entworfen, die sie heute erfüllt. Sie entwickelte sich aus der anfänglich internen API zwischen dem C-Code des Interpreters und der Python-Sprache und -Bibliotheken. In ihrer ersten Inkarnation wurde sie verfügbar gemacht, um Python in C/C++-Anwendungen einbetten und Erweiterungsmodule in C/C++ schreiben zu können. Diese Fähigkeiten waren für das Wachstum von Pythons Ökosystem von grundlegender Bedeutung. Über die Jahrzehnte hinweg entwickelte sich die C-API weiter, um verschiedene Stabilitätsstufen bereitzustellen, Konventionen änderten sich und neue Nutzungsmuster entstanden, wie z. B. Bindungen zu anderen Sprachen als C/C++. In den nächsten Jahren werden neue Entwicklungen die C-API weiter auf die Probe stellen, wie z. B. die Entfernung des GIL und die Entwicklung eines JIT-Compilers. Dieses Wachstum wurde jedoch nicht durch klar dokumentierte Richtlinien unterstützt, was zu inkonsistenten Ansätzen beim API-Design in verschiedenen Subsystemen von CPython führte. Darüber hinaus ist CPython nicht mehr die einzige Implementierung von Python, und einige der Designentscheidungen, die damals getroffen wurden, sind für alternative Implementierungen schwer zu handhaben [Issue 64]. In der Zwischenzeit wurden Lehren gezogen und Fehler sowohl im Design als auch in der Implementierung der C-API identifiziert.

Die Weiterentwicklung der C-API ist aufgrund der Kombination aus Abwärtskompatibilitätsbeschränkungen und ihrer inhärenten technischen und sozialen Komplexität schwierig. Verschiedene Arten von Benutzern bringen unterschiedliche, manchmal widersprüchliche Anforderungen mit. Der Kompromiss zwischen Stabilität und Fortschritt ist ein fortlaufendes, stark umstrittenes Diskussionsthema, wenn Vorschläge für inkrementelle Verbesserungen gemacht werden. Mehrere Vorschläge zur Verbesserung, Neugestaltung oder zum Ersatz der C-API wurden vorgelegt, die jeweils eine tiefe Analyse der Probleme darstellen. Auf dem Language Summit 2023 waren drei aufeinanderfolgende Sitzungen verschiedenen Aspekten der C-API gewidmet. Es besteht allgemeine Einigkeit darüber, dass ein neues Design die Probleme beheben kann, die sich in den letzten 30 Jahren in der C-API angesammelt haben, und sie gleichzeitig für Anwendungsfälle aktualisieren kann, für die sie ursprünglich nicht konzipiert wurde.

Auf dem Language Summit wurde jedoch auch der Eindruck gewonnen, dass wir versuchen, Lösungen zu diskutieren, ohne ein klares gemeinsames Verständnis der Probleme zu haben, die wir lösen wollen. Wir haben beschlossen, dass wir uns auf die aktuellen Probleme mit der C-API einigen müssen, bevor wir in der Lage sind, die vorgeschlagenen Lösungen zu bewerten. Wir haben daher das capi-workgroup Repository auf GitHub erstellt, um die Ideen aller zu diesem Thema zu sammeln.

Über 60 verschiedene Probleme wurden in diesem Repository erstellt, die jeweils ein Problem mit der C-API beschreiben. Wir haben sie kategorisiert und eine Reihe von wiederkehrenden Themen identifiziert. Die folgenden Abschnitte entsprechen weitgehend diesen Themen und enthalten jeweils eine kombinierte Beschreibung der in dieser Kategorie aufgeworfenen Probleme sowie Links zu den einzelnen Problemen. Zusätzlich haben wir einen Abschnitt aufgenommen, der die verschiedenen Stakeholder der C-API und die spezifischen Anforderungen, die jeder von ihnen hat, identifizieren soll.

C-API-Stakeholder

Wie in der Einleitung erwähnt, wurde die C-API ursprünglich als interne Schnittstelle zwischen CPythons Interpreter und der Python-Schicht geschaffen. Sie wurde später als Möglichkeit für Drittentwickler zur Erweiterung und Einbettung von Python-Programmen bereitgestellt. Im Laufe der Jahre entstanden neue Arten von Stakeholdern mit unterschiedlichen Anforderungen und Schwerpunkten. Dieser Abschnitt beschreibt diesen komplexen Sachverhalt anhand der Aktionen, die verschiedene Stakeholder über die C-API ausführen müssen.

Gemeinsame Aktionen für alle Stakeholder

Es gibt Aktionen, die generisch sind und von allen Arten von API-Benutzern benötigt werden

  • Funktionen definieren und aufrufen
  • Neue Typen definieren
  • Instanzen von eingebauten und benutzerdefinierten Typen erstellen
  • Operationen auf Objektinstanzen durchführen
  • Objekte introspezieren, einschließlich Typen, Instanzen und Funktionen
  • Ausnahmen auslösen und behandeln
  • Module importieren
  • Zugriff auf die OS-Schnittstelle von Python

Die folgenden Abschnitte befassen sich mit den einzigartigen Anforderungen verschiedener Stakeholder.

Erweiterungsschreiber

Erweiterungsschreiber sind die traditionellen Benutzer der C-API. Ihre Anforderungen sind die oben genannten gemeinsamen Aktionen. Sie müssen auch häufig

  • Neue Module erstellen
  • Effiziente Schnittstelle zwischen Modulen auf C-Ebene

Autoren von eingebetteten Python-Anwendungen

Anwendungen mit einem eingebetteten Python-Interpreter. Beispiele sind Blender und OBS.

Sie müssen in der Lage sein

  • Den Interpreter konfigurieren (Importpfade, inittab, sys.argv, Speicherallokator usw.).
  • Mit dem Ausführungsmodell und der Programmlebensdauer interagieren, einschließlich sauberer Interpreterabschaltung und -neustart.
  • Komplexe Datenmodelle so darstellen, dass Python sie ohne tiefe Kopien verwenden kann.
  • Eingefrorene Module bereitstellen und importieren.
  • Mehrere unabhängige Interpreter ausführen und verwalten (insbesondere, wenn sie in einer Bibliothek eingebettet sind, die globale Effekte vermeiden möchte).

Python-Implementierungen

Python-Implementierungen wie CPython, PyPy, GraalPy, IronPython, RustPython, MicroPython und Jython), können sehr unterschiedliche Ansätze für die Implementierung verschiedener Subsysteme verfolgen. Sie benötigen

  • Die API muss abstrakt sein und Implementierungsdetails verbergen.
  • Eine Spezifikation der API, idealerweise mit einer Testsuite, die die Kompatibilität gewährleistet.
  • Es wäre schön, eine ABI zu haben, die über Python-Implementierungen hinweg geteilt werden kann.

Alternative APIs und Binding-Generatoren

Es gibt mehrere Projekte, die Alternativen zur C-API implementieren, welche Erweiterungsprogrammierern Vorteile gegenüber der direkten Programmierung mit der C-API bieten. Diese APIs werden mit der C-API implementiert und in einigen Fällen unter Verwendung von CPython-Interna.

Es gibt auch Bibliotheken, die Bindungen zwischen Python und anderen Objektmodellen, Paradigmen oder Sprachen erstellen.

Es gibt Überschneidungen zwischen diesen Kategorien: Binding-Generatoren stellen in der Regel alternative APIs bereit und umgekehrt.

Beispiele sind Cython, cffi, pybind11 und nanobind für C++, PyO3 für Rust, Shiboken, das von PySide für Qt verwendet wird, PyGObject für GTK, Pygolo für Go, JPype für Java, PyJNIus für Android, PyObjC für Objective-C, SWIG für C/C++, Python.NET für .NET (C#), HPy, Mypyc, Pythran und pythoncapi-compat. CPythons DSL zum Parsen von Funktionsargumenten, das Argument Clinic, kann ebenfalls zu dieser Kategorie von Stakeholdern gezählt werden.

Alternative APIs benötigen minimale Bausteine für den effizienten Zugriff auf CPython. Sie benötigen nicht unbedingt eine ergonomische API, da sie normalerweise Code generieren, der nicht für Menschen bestimmt ist. Sie benötigen sie jedoch ausreichend umfassend, damit sie den Zugriff auf Interna vermeiden können, ohne die Leistung zu beeinträchtigen.

Binding-Generatoren müssen oft

  • Benutzerdefinierte Objekte (z. B. Funktions-/Modulobjekte und Traceback-Einträge) erstellen, die dem Verhalten des entsprechenden Python-Codes so genau wie möglich entsprechen.
  • Dynamisch Objekte erstellen, die in traditionellen C-Erweiterungen statisch sind (z. B. Klassen/Module), und die CPython zur Verwaltung ihres Zustands und ihrer Lebensdauer benötigen.
  • Fremde Objekte (Zeichenketten, GC-verwaltete Container) mit geringem Overhead dynamisch anpassen.
  • Externe Mechanismen, Ausführungsmodelle und Garantien an die Python-Art anpassen (Stackful-Coroutinen, Fortsetzungen, One-Writer-or-Multiple-Readers-Semantik, virtuelle Mehrfachvererbung, 1-basierte Indizierung, super-lange Vererbungsketten, Goroutinen, Kanäle usw.).

Diese Werkzeuge könnten auch von einer Wahl zwischen einer stabileren und einer schnelleren (möglicherweise niedrigeren) API profitieren. Ihre Benutzer könnten dann entscheiden, ob sie sich eine häufige Neugenerierung des Codes leisten können oder ob sie einige Leistungseinbußen gegen mehr Stabilität und weniger Wartungsaufwand eintauschen.

Stärken der C-API

Während der Großteil dieses Dokuments den Problemen mit der C-API gewidmet ist, die wir in jedem neuen Design behoben sehen möchten, ist es auch wichtig, die Stärken der C-API hervorzuheben und sicherzustellen, dass sie erhalten bleiben.

Wie in der Einleitung erwähnt, ermöglichte die C-API die Entwicklung und das Wachstum des Python-Ökosystems in den letzten drei Jahrzehnten und entwickelte sich weiter, um Anwendungsfälle zu unterstützen, für die sie ursprünglich nicht konzipiert wurde. Diese Erfolgsbilanz ist an sich schon ein Hinweis darauf, wie effektiv und wertvoll sie war.

Eine Reihe spezifischer Stärken wurde in den Diskussionen der capi-workgroup genannt. Heap-Typen wurden als viel sicherer und einfacher zu verwenden als statische Typen identifiziert [Issue 4].

API-Funktionen, die einen C-String-Literal für Lookups basierend auf einem Python-String akzeptieren, sind sehr praktisch [Issue 30].

Die begrenzte API zeigt, dass eine API, die Implementierungsdetails verbirgt, die Weiterentwicklung von Python erleichtert [Issue 30].

Probleme der C-API

Der Rest dieses Dokuments fasst die auf dem Repository capi-workgroup gemeldeten Probleme zusammen und kategorisiert sie. Die Probleme sind in mehrere Kategorien unterteilt.

API-Evolution und Wartung

Die Schwierigkeit, Änderungen in der C-API vorzunehmen, steht im Mittelpunkt dieses Berichts. Sie ist in vielen der hier diskutierten Probleme implizit enthalten, insbesondere wenn wir entscheiden müssen, ob ein inkrementeller Bugfix das Problem lösen kann oder ob es nur im Rahmen einer API-Neugestaltung angegangen werden kann [Issue 44]. Der Nutzen jeder inkrementellen Änderung wird oft als zu gering erachtet, um die Störung zu rechtfertigen. Im Laufe der Zeit bedeutet dies, dass jeder Fehler, den wir beim Design oder der Implementierung einer API machen, uns auf unbestimmte Zeit erhalten bleibt.

Wir können dieses Problem aus zwei Blickwinkeln betrachten. Einerseits ist dies ein Problem und die Lösung muss in jede neue C-API integriert werden, die wir entwerfen, in Form eines Prozesses für die inkrementelle API-Evolution, der die Deprekation und Entfernung von API-Elementen beinhaltet. Der andere mögliche Ansatz ist, dass dies kein zu lösendes Problem, sondern eher ein Merkmal jeder API ist. In dieser Sichtweise sollte die API-Evolution nicht inkrementell erfolgen, sondern durch große Neugestaltungen, die jeweils aus den Fehlern der Vergangenheit lernen und nicht durch Rückwärtskompatibilitätsanforderungen gefesselt sind (in der Zwischenzeit können neue API-Elemente hinzugefügt werden, aber nichts kann jemals entfernt werden). Ein Kompromissansatz liegt irgendwo zwischen diesen beiden Extremen, wobei Probleme, die leicht oder wichtig genug sind, inkrementell angegangen werden, und andere unberührt bleiben.

Das Problem, das wir in CPython haben, ist, dass wir keinen vereinbarten, offiziellen Ansatz für die API-Evolution haben. Verschiedene Mitglieder des Kernteams ziehen in unterschiedliche Richtungen, und dies ist eine ständige Quelle von Meinungsverschiedenheiten. Jede neue C-API muss mit einer klaren Entscheidung über das Modell, das ihre Wartung befolgen wird, sowie über die technischen und organisatorischen Prozesse, mit denen dies geschehen wird, einhergehen.

Wenn das Modell Bestimmungen für die inkrementelle Evolution der API enthält, wird es Prozesse zur Verwaltung der Auswirkungen der Änderung auf die Benutzer [Issue 60] beinhalten, vielleicht durch die Einführung eines externen Abwärtskompatibilitätsmoduls [Issue 62] oder eine neue API-Schicht von „gesegneten“ Funktionen [Issue 55].

API-Spezifikation und Abstraktion

Die C-API hat keine formelle Spezifikation; sie ist derzeit als das definiert, was die Referenzimplementierung (CPython) in einer bestimmten Version enthält. Die Dokumentation dient als unvollständige Beschreibung, die nicht ausreicht, um die Korrektheit der vollständigen API, der begrenzten API oder der stabilen ABI zu überprüfen. Infolgedessen kann sich die C-API zwischen den Versionen erheblich ändern, ohne dass eine sichtbarere Spezifikationsaktualisierung erforderlich ist, was zu einer Reihe von Problemen führt.

Bindungen für Sprachen außer C/C++ müssen C-Code parsen [Issue 7]. Einige C-Sprachfeatures sind in dieser Weise schwer zu handhaben, da sie Compiler-abhängige Ausgaben (wie Enums) erzeugen oder einen C-Präprozessor/Compiler anstelle eines reinen Parsers erfordern (wie Makros) [Issue 35].

Darüber hinaus neigen C-Header-Dateien dazu, mehr als für die öffentliche API vorgesehen offenzulegen [Issue 34]. Insbesondere Implementierungsdetails wie die genauen Speicherlayouts interner Datenstrukturen können offengelegt werden [Issue 22 und PEP 620]. Dies kann die API-Entwicklung sehr schwierig machen, insbesondere wenn sie in der stabilen ABI auftritt, wie im Fall von ob_refcnt und ob_type, auf die über die Makros zur Referenzzählung zugegriffen wird [Issue 45].

Wir haben ein tieferes Problem in Bezug auf die Art und Weise identifiziert, wie die Referenzzählung offengelegt wird. Die Art und Weise, wie C-Erweiterungen Referenzen mit Aufrufen von Py_INCREF und Py_DECREF verwalten müssen, ist spezifisch für CPythons Speichermodell und für alternative Python-Implementierungen schwer zu emulieren. [Issue 12].

Ein weiterer Satz von Problemen ergibt sich aus der Tatsache, dass ein PyObject* in der C-API als tatsächlicher Zeiger und nicht als Handle offengelegt wird. Die Adresse eines Objekts dient als dessen ID und wird für Vergleiche verwendet, was die Angelegenheit für alternative Python-Implementierungen erschwert, die Objekte während der GC verschieben [Issue 37].

Ein separates Problem ist, dass Objektverweise für die Laufzeit undurchsichtig sind und nur durch Aufrufe von tp_traverse/tp_clear entdeckt werden können, die ihre eigenen Zwecke haben. Wenn es einen Weg gäbe, dass die Laufzeit die Struktur des Objektgraphen kennt und mit Änderungen darin Schritt halten kann, wäre es alternativen Implementierungen möglich, verschiedene Speicherverwaltungsschemata zu implementieren [Issue 33].

Objekt-Referenzverwaltung

Es gibt keine konsistente Namenskonvention für Funktionen, die ihre Referenzsemantik offensichtlich macht, und dies führt zu fehleranfälligen C-API-Funktionen, bei denen sie nicht dem typischen Verhalten folgen. Wenn eine C-API-Funktion einen PyObject* zurückgibt, erwirbt der Aufrufer typischerweise die Eigentümerschaft einer Referenz auf das Objekt. Es gibt jedoch Ausnahmen, bei denen eine Funktion eine „geliehene“ Referenz zurückgibt, die der Aufrufer zugreifen kann, aber keine Referenz besitzt. Ebenso ändern Funktionen typischerweise nicht die Eigentümerschaft von Referenzen auf ihre Argumente, aber es gibt Ausnahmen, bei denen eine Funktion eine Referenz „stiehlt“, d. h. die Eigentümerschaft der Referenz wird durch den Aufruf vom Aufrufer auf den Aufgerufenen übertragen [Issue 8 und Issue 52]. Die Terminologie, die zur Beschreibung dieser Situationen in der Dokumentation verwendet wird, kann ebenfalls verbessert werden [Issue 11].

Eine radikalere Änderung ist bei Funktionen erforderlich, die „geliehene“ Referenzen zurückgeben (wie PyList_GetItem) [Issue 5 und Issue 21] oder Zeiger auf Teile der internen Struktur eines Objekts (wie PyBytes_AsString) [Issue 57]. In beiden Fällen ist die Referenz/der Zeiger so lange gültig, wie das besitzende Objekt die Referenz hält, aber diese Zeit ist schwer zu begründen. Solche Funktionen sollten in der API nicht ohne einen Mechanismus existieren, der sie sicher machen kann.

Für Container fehlen in der API derzeit Bulk-Operationen auf den Referenzen der enthaltenen Objekte. Dies ist besonders wichtig für eine stabile ABI, bei der INCREF und DECREF keine Makros sein können, was Bulk-Operationen bei Implementierung als Sequenz von Funktionsaufrufen teuer macht [Issue 15].

Typdefinition und Objekterstellung

Die C-API hat Funktionen, die die Erstellung unvollständiger oder inkonsistenter Python-Objekte ermöglichen, wie z. B. PyTuple_New und PyUnicode_New. Dies verursacht Probleme, wenn das Objekt vom GC verfolgt wird oder seine tp_traverse/tp_clear Funktionen aufgerufen werden. Ein verwandtes Problem betrifft Funktionen wie PyTuple_SetItem, die verwendet werden, um ein teilweise initialisiertes Tupel zu ändern (Tupel sind nach vollständiger Initialisierung unveränderlich) [Issue 56].

Wir haben einige Probleme mit Typdefinitions-APIs identifiziert. Aus historischen Gründen gibt es oft eine erhebliche Code-Duplizierung zwischen tp_new und tp_vectorcall [Issue 24]. Die Typ-Slot-Funktion sollte indirekt aufgerufen werden, damit ihre Signaturen geändert werden können, um Kontextinformationen einzuschließen [Issue 13]. Mehrere Aspekte des Typdefinitions- und Erstellungsprozesses sind nicht gut definiert, z. B. welche Phase des Prozesses für die Initialisierung und Löschung verschiedener Felder des Typobjekts verantwortlich ist [Issue 49].

Fehlerbehandlung

Die Fehlerbehandlung in der C-API basiert auf dem Fehlerindikator, der im Thread-Status (im globalen Geltungsbereich) gespeichert wird. Die Designabsicht war, dass jede API-Funktion einen Wert zurückgibt, der angibt, ob ein Fehler aufgetreten ist (konventionell -1 oder NULL). Wenn das Programm weiß, dass ein Fehler aufgetreten ist, kann es das Ausnahmeobjekt abrufen, das im Fehlerindikator gespeichert ist. Wir haben eine Reihe von Problemen im Zusammenhang mit der Fehlerbehandlung identifiziert, die auf APIs hinweisen, die zu leicht falsch verwendet werden können.

Es gibt Funktionen, die nicht alle Fehler melden, die während ihrer Ausführung auftreten. Zum Beispiel löscht PyDict_GetItem alle Fehler, die auftreten, wenn die Hash-Funktion des Schlüssels aufgerufen wird oder während einer Suche im Wörterbuch [Issue 51].

Python-Code wird per Definition nie mit einer im Gange befindlichen Ausnahme ausgeführt, und typischerweise sollte auch Code, der native Funktionen verwendet, durch eine ausgelöste Ausnahme unterbrochen werden. Dies wird in den meisten C-API-Funktionen nicht geprüft, und es gibt Stellen im Interpreter, an denen der Fehlerbehandlungscode eine C-API-Funktion aufruft, während eine Ausnahme gesetzt ist. Siehe zum Beispiel den Aufruf von PyUnicode_FromString im Fehlerbehandler von _PyErr_WriteUnraisableMsg [Issue 2].

Es gibt Funktionen, die keinen Wert zurückgeben, sodass ein Aufrufer gezwungen ist, den Fehlerindikator abzufragen, um festzustellen, ob ein Fehler aufgetreten ist. Ein Beispiel ist PyBuffer_Release [Issue 20]. Es gibt andere Funktionen, die einen Rückgabewert haben, aber dieser Rückgabewert zeigt nicht eindeutig an, ob ein Fehler aufgetreten ist. Zum Beispiel gibt PyLong_AsLong im Fehlerfall -1 zurück, oder wenn der Wert des Arguments tatsächlich -1 ist [Issue 1]. In beiden Fällen ist die API fehleranfällig, da es möglich ist, dass der Fehlerindikator bereits gesetzt war, bevor die Funktion aufgerufen wurde, und der Fehler fälschlicherweise zugeordnet wird. Die Tatsache, dass der Fehler vor dem Aufruf nicht erkannt wurde, ist ein Fehler im aufrufenden Code, aber das Verhalten des Programms in diesem Fall macht es nicht einfach, das Problem zu identifizieren und zu debuggen.

Es gibt Funktionen, die ein PyObject*-Argument entgegennehmen, mit besonderer Bedeutung, wenn es NULL ist. Wenn beispielsweise PyObject_SetAttr NULL als zu setzenden Wert erhält, bedeutet dies, dass das Attribut gelöscht werden soll. Dies ist fehleranfällig, da NULL einen Fehler bei der Konstruktion des Wertes bedeuten könnte und das Programm diesen Fehler nicht überprüft hat. Das Programm wird NULL falsch interpretieren und nicht als Fehler [Issue 47].

API-Schichten und Stabilitätsgarantien

Die verschiedenen API-Schichten bieten unterschiedliche Kompromisse zwischen Stabilität, API-Evolution und manchmal Leistung.

Die stabile ABI wurde als ein Bereich identifiziert, der untersucht werden muss. Derzeit ist sie unvollständig und nicht weit verbreitet. Gleichzeitig erschwert ihre Existenz Änderungen an einigen Implementierungsdetails, da sie Strukturfelder wie ob_refcnt, ob_type und ob_size offenlegt. Es gab einige Diskussionen darüber, ob die stabile ABI es wert ist, beibehalten zu werden. Argumente für beide Seiten finden sich in [Issue 4] und [Issue 9].

Alternativ wurde vorgeschlagen, dass wir, um die stabile ABI weiterentwickeln zu können, einen Mechanismus zur Unterstützung mehrerer Versionen davon in derselben Python-Binärdatei benötigen. Es wurde darauf hingewiesen, dass die Versionierung einzelner Funktionen innerhalb einer einzelnen ABI-Version nicht ausreicht, da es notwendig sein kann, eine Gruppe von Funktionen, die miteinander interagieren, gemeinsam weiterzuentwickeln [Issue 39].

Die begrenzte API wurde in 3.2 als eine „gesegnete“ Teilmenge der C-API eingeführt, die für Benutzer empfohlen wird, die sich auf qualitativ hochwertige APIs beschränken möchten, die sich wahrscheinlich nicht oft ändern. Das Flag Py_LIMITED_API ermöglicht es Benutzern, ihr Programm auf ältere Versionen der begrenzten API zu beschränken, aber wir benötigen jetzt die umgekehrte Option, um ältere Versionen auszuschließen. Dies würde es ermöglichen, die begrenzte API durch den Ersatz fehlerhafter Elemente zu entwickeln [Issue 54]. Allgemeiner sollten wir bei einer Neugestaltung die Art und Weise, wie API-Schichten spezifiziert werden, überdenken und eine Methode entwickeln, die die Art und Weise vereinheitlicht, wie wir derzeit zwischen den verschiedenen Schichten wählen [Issue 59].

API-Elemente, deren Namen mit einem Unterstrich beginnen, gelten als privat, im Wesentlichen eine API-Schicht ohne Stabilitätsgarantien. Dies wurde jedoch erst kürzlich in PEP 689 geklärt. Es ist unklar, wie die Änderungsrichtlinie für solche API-Elemente aussehen soll, die vor PEP 689 bestehen [Issue 58].

Es gibt API-Funktionen, die eine unsichere (aber schnelle) Version sowie eine sichere Version mit Fehlerprüfung haben (z. B. PyTuple_GET_ITEM vs PyTuple_GetItem). Es könnte hilfreich sein, diese in eigene Schichten einzuteilen – die Schicht „unsichere API“ und die Schicht „sichere API“ [Issue 61].

Verwendung der C-Sprache

Es wurden mehrere Probleme hinsichtlich der Art und Weise aufgeworfen, wie CPython die C-Sprache verwendet. Erstens gibt es die Frage, welchen C-Dialekt wir verwenden und wie wir unsere Kompatibilität damit testen, sowie die Kompatibilität von API-Headern mit C++-Dialekten [Issue 42].

Die Verwendung von const in der API ist derzeit spärlich, aber es ist unklar, ob dies etwas ist, das wir ändern sollten [Issue 38].

Wir verwenden derzeit die C-Typen long und int, wo feste Integer wie int32_t und int64_t jetzt möglicherweise bessere Wahlmöglichkeiten sind [Issue 27].

Wir verwenden C-Sprachmerkmale, mit denen andere Sprachen nur schwer interagieren können, wie Makros, variable Argumentlisten, Aufzählungen, Bitfelder und Nicht-Funktionssymbole [Issue 35].

Es gibt API-Funktionen, die ein PyObject* Argument entgegennehmen, das von einem spezifischeren Typ sein muss (wie z. B. PyTuple_Size, das fehlschlägt, wenn sein Argument kein PyTupleObject* ist). Es ist eine offene Frage, ob dies ein gutes Muster ist oder ob die API den spezifischeren Typ erwarten sollte [Issue 31].

Es gibt Funktionen in der API, die konkrete Typen verwenden, wie z. B. PyDict_GetItemString, die eine Dictionary-Suche nach einem als C-String angegebenen Schlüssel durchführt und nicht nach einem PyObject*. Gleichzeitig wird für PyDict_ContainsString nicht als angemessen erachtet, eine konkrete Typ-Alternative hinzuzufügen. Das Prinzip hierzu sollte in den Richtlinien dokumentiert werden [Issue 23].

Implementierungsfehler

Nachfolgend finden Sie eine Liste lokalisierter Implementierungsfehler. Die meisten davon können wahrscheinlich schrittweise behoben werden, wenn wir uns dafür entscheiden. Sie sollten in jedem Fall bei jedem neuen API-Design vermieden werden.

Es gibt Funktionen, die nicht der Konvention folgen, 0 für Erfolg und -1 für Fehler zurückzugeben. Zum Beispiel gibt PyArg_ParseTuple 0 für Erfolg und einen Nicht-Null-Wert für Fehler zurück [Issue 25].

Die Makros Py_CLEAR und Py_SETREF greifen mehr als einmal auf ihr Argument zu, sodass, wenn das Argument ein Ausdruck mit Nebeneffekten ist, diese dupliziert werden [Issue 3].

Die Bedeutung von Py_SIZE hängt vom Typ ab und ist nicht immer zuverlässig [Issue 10].

Einige API-Funktionen verhalten sich nicht gleich wie ihre Python-Äquivalente. Das Verhalten von PyIter_Next unterscheidet sich von tp_iternext. [Issue 29]. Das Verhalten von PySet_Contains unterscheidet sich von set.__contains__ [Issue 6].

Die Tatsache, dass PyArg_ParseTupleAndKeywords ein nicht-const char* Array als Argument akzeptiert, erschwert die Verwendung [Issue 28].

Python.h gibt nicht die gesamte API frei. Einige Header (wie marshal.h) sind nicht von Python.h enthalten. [Issue 43].

Benennung

PyLong und PyUnicode verwenden Namen, die nicht mehr mit den Python-Typen übereinstimmen, die sie repräsentieren (int/str). Dies könnte in einer neuen API behoben werden [Issue 14].

Es gibt Bezeichner in der API, denen ein Py/_Py Präfix fehlt [Issue 46].

Fehlende Funktionalität

Dieser Abschnitt besteht aus einer Liste von Funktionswünschen, d. h. Funktionalität, die in der aktuellen C-API als fehlend identifiziert wurde.

Debug-Modus

Ein Debug-Modus, der ohne Neukompilierung aktiviert werden kann und verschiedene Prüfungen aktiviert, die bei der Erkennung verschiedener Fehlertypen helfen können [Issue 36].

Introspektion

Es gibt derzeit keine zuverlässigen Introspektionsmöglichkeiten für in C definierte Objekte, wie es sie für Python-Objekte gibt [Issue 32].

Effiziente Typprüfung für Heap-Typen [Issue 17].

Verbesserte Interaktion mit anderen Sprachen

Schnittstellen mit anderen GC-basierten Sprachen und Integration ihres GC mit Pythons GC [Issue 19].

Einschleusen von fremden Stack-Frames in den Traceback [Issue 18].

Konkrete Strings, die in anderen Sprachen verwendet werden können [Issue 16].

Referenzen

  1. Python/C API Referenzhandbuch
  2. Blogbeitrag zum Language Summit 2023: Drei Vorträge über die C-API
  3. capi-workgroup auf GitHub
  4. Folien von Irit's Core Sprint 2023 zur C API Workgroup
  5. Folien von Petr's Core Sprint 2023
  6. Folien des HPy-Teams vom Core Sprint 2023 über Dinge, die man von HPy lernen kann
  7. Folien von Victor vom Core Sprint 2023 Python C API Vortrag
  8. Pythons Stabilitätsversprechen – Cristián Maureira-Fredes, PySide Maintainer
  9. Bericht über die Probleme, die PySide vor 5 Jahren beim Wechsel zur stabilen ABI hatte

Quelle: https://github.com/python/peps/blob/main/peps/pep-0733.rst

Zuletzt geändert: 2024-10-28 18:52:58 GMT