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

Python Enhancement Proposals

PEP 576 – Rationalisierung von eingebauten Funktionsklassen

Autor:
Mark Shannon <mark at hotpy.org>
BDFL-Delegate:
Petr Viktorin
Status:
Zurückgezogen
Typ:
Standards Track
Erstellt:
10. Mai 2018
Python-Version:
3.8
Post-History:
17. Mai 2018, 23. Juni 2018, 08. Juli 2018, 29. März 2019

Inhaltsverzeichnis

Zusammenfassung

Die intern von CPython verwendete „FastcallKeywords“-Konvention für Callable-Objekte von Drittanbietern freigeben und das Modul inspect zur Duck-Typisierung verwenden. In Kombination wird dies Drittanbieter-C-Erweiterungen und Tools wie Cython ermöglichen, Objekte zu erstellen, die die gleichen Aufrufkonventionen wie eingebaute und Python-Funktionen verwenden und somit Leistungsparität mit eingebauten Funktionen wie len oder print erzielen.

Eine geringfügige Leistungsverbesserung bestehenden Codes wird erwartet.

Motivation

Derzeit stehen Autoren von Modulen von Drittanbietern vor einem Dilemma bei der Implementierung von Funktionen in C. Entweder sie können eine der bereits vorhandenen eingebauten Funktions- oder Methodenklassen verwenden oder sie implementieren ihre eigene benutzerdefinierte Klasse in C. Die erste Wahl führt zum Verlust der Fähigkeit, auf die Interna des Callable-Objekts zuzugreifen. Die zweite Wahl stellt eine zusätzliche Wartungsbelastung dar und, was noch wichtiger ist, hat erhebliche negative Auswirkungen auf die Leistung.

Dieser PEP zielt darauf ab, Autoren von C-Modulen von Drittanbietern und Tools wie Cython die schnellere Aufrufkonvention zu ermöglichen, die von CPython intern für eingebaute Funktionen und Methoden verwendet wird, und dies ohne Verlust von Funktionen im Vergleich zu einer in Python implementierten Funktion.

Introspektion

Das Modul inspect unterstützt die Duck-Typisierung bei der Introspektion von Callable-Objekten vollständig.

Die Funktion inspect.Signature.from_callable() berechnet die Signatur eines Callable-Objekts. Wenn ein Objekt eine __signature__-Eigenschaft hat, gibt inspect.Signature.from_callable() diese einfach zurück. Zur weiteren Unterstützung der Duck-Typisierung, wenn ein Callable-Objekt eine __text_signature__ hat, wird die __signature__ daraus erstellt.

Das bedeutet, dass 3rd-Party-Builtin-Funktionen __text_signature__ implementieren können, wenn dies ausreichend ist, und die aufwändigere __signature__, wenn nötig.

Effiziente Aufrufe von Callable-Objekten von Drittanbietern

Derzeit werden die meisten Aufrufe an function und method_descriptor in benutzerdefiniertem Code unter Verwendung der internen „FastcallKeywords“-Aufrufkonvention weitergeleitet. Dieser PEP schlägt vor, dass diese Aufrufkonvention über einen C-Funktionszeiger implementiert wird. Drittanbieter-Callable-Objekte, die diese Binärschnittstelle implementieren, haben das Potenzial, so schnell aufgerufen zu werden wie eine eingebaute Funktion.

Fortgesetztes Verbot von Callable-Klassen als Basisklassen

Derzeit schlägt jeder Versuch, function, method oder method_descriptor als Basisklasse für eine neue Klasse zu verwenden, mit einem TypeError fehl. Dieses Verhalten ist wünschenswert, da es Fehler vermeidet, wenn eine Unterklasse die __call__-Methode überschreibt. Wenn Callable-Objekte Unterklassen bilden könnten, müsste jeder Aufruf einer function oder eines method_descriptor eine zusätzliche Prüfung beinhalten, ob die __call__-Methode nicht überschrieben wurde. Durch die Bereitstellung eines zusätzlichen Aufrufmechanismus wird das Fehlerrisiko größer. Infolgedessen kann jede Drittanbieter-Klasse, die die zusätzliche Aufrufschnittstelle implementiert, nicht als Basisklasse verwendet werden.

Neue Klassen und Änderungen an bestehenden Klassen

Für Python sichtbare Änderungen

  1. Eine neue eingebaute Klasse, builtin_function, wird hinzugefügt.
  2. types.BuiltinFunctionType wird auf builtin_function verweisen und nicht auf builtin_function_or_method.
  3. Instanzen der Klasse builtin_function behalten die __module__-Eigenschaft von builtin_function_or_method und erhalten die Eigenschaften func_module und func_globals. func_module ermöglicht den Zugriff auf das Modul, zu dem die Funktion gehört. Beachten Sie, dass dies anders ist als die Eigenschaft __module__, die lediglich den Namen des Moduls zurückgibt. Die Eigenschaft func_globals ist äquivalent zu func_module.__dict__ und wird bereitgestellt, um die gleichnamige Eigenschaft der Python-Funktion zu emulieren.
  4. Beim Binden einer Instanz von method_descriptor an eine Instanz ihrer besitzenden Klasse wird anstelle von builtin_function_or_method eine bound_method erstellt. Das bedeutet, dass die method_descriptors nun das Verhalten von Python-Funktionen genauer emulieren. Mit anderen Worten, [].append wird zu einer bound_method und nicht zu einer builtin_function_or_method.

Änderungen an der C-API

  1. Eine neue Funktion PyBuiltinFunction_New(PyMethodDef *ml, PyObject *module) wird hinzugefügt, um eingebaute Funktionen zu erstellen.
  2. PyCFunction_NewEx() und PyCFunction_New() werden als veraltet markiert und geben, wenn möglich, eine PyBuiltinFunction zurück, andernfalls eine builtin_function_or_method.

Aufrechterhaltung der Abwärtskompatibilität bei der C-API und ABI

Die vorgeschlagenen Änderungen sind auf API- und ABI-Ebene vollständig abwärts- und vorwärtskompatibel.

Interne C-Änderungen

Zwei neue Flags dürfen für das Feld typeobject.tp_flags verwendet werden. Dies sind Py_TPFLAGS_EXTENDED_CALL und Py_TPFLAGS_FUNCTION_DESCRIPTOR

Py_TPFLAGS_EXTENDED_CALL

Für jede eingebaute Klasse, die Py_TPFLAGS_EXTENDED_CALL setzt, muss die C-Struktur, die dieser eingebauten Klasse entspricht, mit der Struktur PyExtendedCallable beginnen, die wie folgt definiert ist:

typedef PyObject *(*extended_call_ptr)(PyObject *callable, PyObject** args,
                   int positional_argcount, PyTupleObject* kwnames);

typedef struct {
    PyObject_HEAD
    extended_call_ptr ext_call;
} PyExtendedCallable;

Jede Klasse, die Py_TPFLAGS_EXTENDED_CALL setzt, kann nicht als Basisklasse verwendet werden, und ein TypeError wird ausgelöst, wenn Python-Code versucht, sie als Basisklasse zu verwenden.

Py_TPFLAGS_FUNCTION_DESCRIPTOR

Wenn dieses Flag für eine eingebaute Klasse F gesetzt ist, wird erwartet, dass Instanzen dieser Klasse sich wie eine Python-Funktion verhalten, wenn sie als Klassenattribut verwendet werden. Insbesondere bedeutet dies, dass der Wert von c.m, wobei C.m eine Instanz der eingebauten Klasse F ist (und c eine Instanz von C ist), eine gebundene Methode sein muss, die C.m und c bindet. Ohne dieses Flag wäre es für benutzerdefinierte Callable-Objekte unmöglich, wie Python-Funktionen zu funktionieren *und* dabei so effizient wie Python- oder eingebaute Funktionen zu sein.

Änderungen an bestehenden C-Strukturen

Die Klassen function, method_descriptor und method werden ihre entsprechenden Strukturen so geändert, dass sie mit der Struktur PyExtendedCallable beginnen.

Drittanbieter-eingebaute Klassen, die die neue erweiterte Aufrufschnittstelle verwenden

Um eine Aufrufsleistung auf dem Niveau von Python-Funktionen und eingebauten Funktionen zu ermöglichen, sollten Drittanbieter-Callable-Objekte das Bit Py_TPFLAGS_EXTENDED_CALL von tp_flags setzen und sicherstellen, dass die entsprechende C-Struktur mit PyExtendedCallable beginnt. Jede eingebaute Klasse, bei der das Bit Py_TPFLAGS_EXTENDED_CALL gesetzt ist, muss auch die Funktion tp_call implementieren und sicherstellen, dass ihr Verhalten mit der Funktion ext_call konsistent ist.

Leistungsimplikationen dieser Änderungen

Das Hinzufügen eines Funktionszeigers zu jedem Callable-Objekt anstelle jeder Klasse von Callable-Objekten ermöglicht die Wahl der Dispatching-Funktion (des Codes zum Mischen von Argumenten und zur Fehlerprüfung) zum Zeitpunkt der Erstellung des Callable-Objekts anstatt zum Zeitpunkt des Aufrufs. Dies sollte die Anzahl der zwischen dem Aufruf-Site im Interpreter und der Ausführung des Aufgerufenen ausgeführten Instruktionen reduzieren.

Alternative Vorschläge

PEP 580 ist ein alternativer Ansatz zur Lösung desselben Problems wie dieser PEP.

Referenzimplementierung

Eine vorläufige Implementierung finden Sie unter https://github.com/markshannon/cpython/tree/pep-576-minimal


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

Zuletzt geändert: 2025-02-01 08:55:40 GMT