PEP 718 – Subscriptable functions
- Autor:
- James Hilton-Balfe <gobot1234yt at gmail.com>
- Sponsor:
- Guido van Rossum <guido at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Entwurf
- Typ:
- Standards Track
- Thema:
- Typisierung
- Erstellt:
- 23-Jun-2023
- Python-Version:
- 3.15
- Post-History:
- 24-Jun-2023
Inhaltsverzeichnis
Zusammenfassung
Diese PEP schlägt vor, Funktionsobjekte für Typisierungszwecke subscriptable (indizierbar) zu machen. Dies gibt Entwicklern die explizite Kontrolle über die Typen, die vom Typ-Checker erzeugt werden, wenn bidirektionale Inferenz (die es ermöglicht, die Typen von Parametern anonymer Funktionen zu inferieren) und andere Methoden als Spezialisierung unzureichend sind. Es bringt auch Funktionen in Bezug auf ihre Fähigkeit, subscriptable zu sein, auf eine Stufe mit regulären Klassen.
Motivation
Unbekannte Typen
Derzeit ist es in bestimmten Situationen nicht möglich, die Typparameter für generische Funktionen zu inferieren
def make_list[T](*args: T) -> list[T]: ...
reveal_type(make_list()) # type checker cannot infer a meaningful type for T
Wenn Instanzen von FunctionType subscriptable wären, könnte dieser Konstruktor typisiert werden
reveal_type(make_list[int]()) # type is list[int]
Derzeit müssen Sie eine Zuweisung verwenden, um einen präzisen Typ anzugeben
x: list[int] = make_list()
reveal_type(x) # type is list[int]
aber dieser Code ist unnötig ausführlich und nimmt mehrere Zeilen für einen einfachen Funktionsaufruf in Anspruch.
Ähnlich kann T in diesem Beispiel derzeit nicht sinnvoll inferiert werden, sodass x ohne eine zusätzliche Zuweisung untyped ist
def factory[T](func: Callable[[T], Any]) -> Foo[T]: ...
reveal_type(factory(lambda x: "Hello World" * x))
Wenn Funktionsobjekte jedoch subscriptable wären, könnte ein spezifischerer Typ angegeben werden
reveal_type(factory[int](lambda x: "Hello World" * x)) # type is Foo[int]
Nicht entscheidbare Inferenz
Es gibt sogar Fälle, in denen Unterklassenbeziehungen die Typinferenz unmöglich machen. Wenn Sie jedoch die Funktion spezialisieren können, können Typ-Checker einen sinnvollen Typ inferieren.
def foo[T](x: Sequence[T] | T) -> list[T]: ...
reveal_type(foo[bytes](b"hello"))
Derzeit synthetisieren Typ-Checker hier keinen konsistenten Typ.
Nicht lösbare Typparameter
Derzeit ist es mit nicht spezialisierten Literalen nicht möglich, einen Typ für Situationen wie diese zu bestimmen
def foo[T](x: list[T]) -> T: ...
reveal_type(foo([])) # type checker cannot infer T (yet again)
reveal_type(foo[int]([])) # type is int
Es ist auch nützlich, in Fällen angeben zu können, in denen ein bestimmter Typ im Voraus an eine Funktion übergeben werden muss
words = ["hello", "world"]
foo[int](words) # Invalid: list[str] is incompatible with list[int]
Die Zulassung von Subscription macht Funktionen und Methoden konsistent mit generischen Klassen, wo sie es nicht bereits waren. Während alle vorgeschlagenen Änderungen mit aufrufbaren generischen Klassen implementiert werden können, wäre syntaktischer Zucker sehr willkommen.
Aus diesem Grund ist die Spezialisierung der Funktion und deren Verwendung als neue Fabrik in Ordnung
make_int_list = make_list[int]
reveal_type(make_int_list()) # type is list[int]
Monomorphisierung und Reifikation
Dieser Vorschlag öffnet auch die Tür für Monomorphisierung und reifizierte Typen.
Dies würde eine Funktionalität ermöglichen, die anekdotisch bereits viele Male angefordert wurde.
Bitte beachten Sie, dass diese Funktion nicht von der PEP vorgeschlagen wird, aber in Zukunft implementiert werden könnte.
Die Syntax für eine solche Funktion könnte wie folgt aussehen
def foo[T]():
return T.__value__
assert foo[int]() is int
Begründung
Funktionsobjekte in dieser PEP bezieht sich auf FunctionType, MethodType, BuiltinFunctionType, BuiltinMethodType und MethodWrapperType.
Für MethodType sollten Sie schreiben können
class Foo:
def make_list[T](self, *args: T) -> list[T]: ...
Foo().make_list[int]()
und es ähnlich wie ein FunctionType funktionieren lassen.
Für BuiltinFunctionType, damit generische Built-in-Funktionen (z. B. max und min) wie in Python definierte funktionieren. Built-in-Funktionen sollten sich so weit wie möglich wie in Python implementierte Funktionen verhalten.
BuiltinMethodType ist vom gleichen Typ wie BuiltinFunctionType.
MethodWrapperType (z. B. der Typ von object().__str__) ist nützlich für generische magische Methoden.
Spezifikation
Funktionsobjekte sollten __getitem__ implementieren, um Subscription zur Laufzeit zu ermöglichen, und eine Instanz von types.GenericAlias zurückgeben, wobei __origin__ als aufrufbar und __args__ als übergebene Typen gesetzt sind.
Typ-Checker sollten die Subscription von Funktionen unterstützen und verstehen, dass die an die Funktions-Subscription übergebenen Parameter denselben Regeln wie eine generische Callable-Klasse folgen sollten.
Setzen von __orig_class__
Derzeit ist __orig_class__ ein Attribut, das in GenericAlias.__call__ auf die Instanz von GenericAlias gesetzt wird, die die aufgerufene Klasse erstellt hat, z. B.
class Foo[T]: ...
assert Foo[int]().__orig_class__ == Foo[int]
Derzeit wird __orig_class__ bedingungslos gesetzt. Um jedoch eine potenzielle Löschung bei erstellten Instanzen zu vermeiden, sollte dieses Attribut nicht gesetzt werden, wenn __origin__ eine Instanz eines beliebigen Funktionsobjekts ist.
Der folgende Code-Schnipsel würde ohne diese Änderung zur Laufzeit fehlschlagen, da __orig_class__ bar[str] und nicht Foo[int] wäre.
def bar[U]():
return Foo[int]()
assert bar[str]().__orig_class__ == Foo[int]
Interaktionen mit @typing.overload
Überladene Funktionen sollten weitgehend wie bisher funktionieren, da sie keine Auswirkungen auf den Laufzeittyp haben. Die einzige Änderung besteht darin, dass mehr Situationen entscheidbar sein werden und das Verhalten/die Überladung vom Entwickler spezifiziert werden kann, anstatt es der Reihenfolge von Überladungen/Vereinigungen zu überlassen.
Abwärtskompatibilität
Derzeit sind diese Klassen nicht unterklassbar, daher gibt es keine Kompatibilitätsprobleme in Bezug auf Klassen, die bereits __getitem__ implementieren.
Referenzimplementierung
Die vorgeschlagenen Änderungen zur Laufzeit finden Sie hier: https://github.com/Gobot1234/cpython/tree/function-subscript
Danksagungen
Vielen Dank an Alex Waygood und Jelle Zijlstra für ihr Feedback zu dieser PEP und an Guido für einige motivierende Beispiele.
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-0718.rst
Zuletzt geändert: 2025-05-06 20:54:28 GMT