PEP 604 – Zulässigkeit der Schreibweise von Union-Typen als X | Y
- Autor:
- Philippe PRADOS <python at prados.fr>, Maggie Moss <maggiebmoss at gmail.com>
- Sponsor:
- Chris Angelico <rosuav at gmail.com>
- BDFL-Delegate:
- Guido van Rossum <guido at python.org>
- Discussions-To:
- Typing-SIG list
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Typisierung
- Erstellt:
- 28-Aug-2019
- Python-Version:
- 3.10
- Post-History:
- 28-Aug-2019, 05-Aug-2020
Zusammenfassung
Diese PEP schlägt vor, den Operator | für Typen zu überladen, um Union[X, Y] als X | Y zu schreiben, und erlaubt dessen Verwendung in isinstance- und issubclass-Aufrufen.
Motivation
PEP 484 und PEP 526 schlagen eine generische Syntax für die Typisierung von Variablen, Parametern und Funktionsrückgaben vor. PEP 585 schlägt vor, Parameter für Generics zur Laufzeit verfügbar zu machen. Mypy [1] akzeptiert eine Syntax, die aussieht wie
annotation: name_type
name_type: NAME (args)?
args: '[' paramslist ']'
paramslist: annotation (',' annotation)* [',']
- Um eine Disjunktion (Union-Typ) zu beschreiben, muss der Benutzer
Union[X, Y]verwenden.
Die Ausführlichkeit dieser Syntax trägt nicht zur Akzeptanz von Typen bei.
Vorschlag
Inspiriert von Scala [2] und Pike [3], fügt dieser Vorschlag den Operator type.__or__() hinzu. Mit diesem neuen Operator ist es möglich, int | str anstelle von Union[int, str] zu schreiben. Zusätzlich zu Annotationen wäre das Ergebnis dieses Ausdrucks dann in isinstance() und issubclass() gültig.
isinstance(5, int | str)
issubclass(bool, int | float)
Wir werden auch in der Lage sein, t | None oder None | t anstelle von Optional[t] zu schreiben.
isinstance(None, int | None)
isinstance(42, None | int)
Spezifikation
Die neue Union-Syntax sollte für Funktions-, Variablen- und Parameterannotationen akzeptiert werden.
Vereinfachte Syntax
# Instead of
# def f(list: List[Union[int, str]], param: Optional[int]) -> Union[float, str]
def f(list: List[int | str], param: int | None) -> float | str:
pass
f([1, "abc"], None)
# Instead of typing.List[typing.Union[str, int]]
typing.List[str | int]
list[str | int]
# Instead of typing.Dict[str, typing.Union[int, float]]
typing.Dict[str, int | float]
dict[str, int | float]
Das bestehende typing.Union und die |-Syntax sollten äquivalent sein.
int | str == typing.Union[int, str]
typing.Union[int, int] == int
int | int == int
Die Reihenfolge der Elemente in der Union sollte für die Gleichheit keine Rolle spielen.
(int | str) == (str | int)
(int | str | float) == typing.Union[str, float, int]
Optionale Werte sollten äquivalent zur neuen Union-Syntax sein.
None | t == typing.Optional[t]
Eine neue `Union.__repr__()`-Methode sollte implementiert werden.
str(int | list[str])
# int | list[str]
str(int | int)
# int
isinstance und issubclass
Die neue Syntax sollte für Aufrufe von isinstance und issubclass akzeptiert werden, solange die Union-Elemente selbst gültige Argumente für isinstance und issubclass sind.
# valid
isinstance("", int | str)
# invalid
isinstance(2, list[int]) # TypeError: isinstance() argument 2 cannot be a parameterized generic
isinstance(1, int | list[int])
# valid
issubclass(bool, int | float)
# invalid
issubclass(bool, bool | list[int])
Inkompatible Änderungen
In einigen Situationen werden einige Ausnahmen nicht wie erwartet ausgelöst.
Wenn eine Metaklasse den Operator __or__ implementiert, wird sie diesen überschreiben.
>>> class M(type):
... def __or__(self, other): return "Hello"
...
>>> class C(metaclass=M): pass
...
>>> C | int
'Hello'
>>> int | C
typing.Union[int, __main__.C]
>>> Union[C, int]
typing.Union[__main__.C, int]
Einwände und Antworten
Weitere Details zu den Diskussionen finden Sie unten.
1. Einen neuen Operator für Union[type1, type2] hinzufügen?
Vorteile
- Diese Syntax kann lesbarer sein und ähnelt anderen Sprachen (Scala, …).
- Zur Laufzeit könnte
int|strin 3.10 ein einfaches Objekt zurückgeben, anstatt alles, was man aus dem Import vontypingholen müsste.
Nachteile
- Das Hinzufügen dieses Operators führt zu einer Abhängigkeit zwischen
typingundbuiltins. - Bricht den Backport (da
typingleicht zurückportiert werden kann, aber die Kern-typesnicht). - Wenn Python selbst nicht geändert werden muss, müssten wir es immer noch in mypy, Pyre, PyCharm, Pytype und wer weiß was noch implementieren (es ist eine geringfügige Änderung, siehe „Referenzimplementierung“).
2. Nur PEP 484 (Type Hints) ändern, um die Syntax type1 | type2 zu akzeptieren?
PEP 563 (Postponed Evaluation of Annotations) reicht aus, um diesen Vorschlag zu akzeptieren, wenn wir uns darauf einigen, nicht mit der dynamischen Auswertung von Annotationen (eval()) kompatibel zu sein.
>>> from __future__ import annotations
>>> def foo() -> int | str: pass
...
>>> eval(foo.__annotations__['return'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'type' and 'type'
3. isinstance() und issubclass() erweitern, um Union zu akzeptieren?
isinstance(x, str | int) ==> "is x an instance of str or int"
Vorteile
- Wenn sie erlaubt wären, könnte die Instanzprüfung eine sehr übersichtliche Notation verwenden.
Nachteile
- Das gesamte
typing-Modul muss inbuiltinmigriert werden.
Referenzimplementierung
Ein neuer integrierter Union-Typ muss implementiert werden, um den Rückgabewert von t1 | t2 zu speichern, und er muss von isinstance() und issubclass() unterstützt werden. Dieser Typ kann im types-Modul platziert werden. Die Interoperabilität zwischen types.Union und typing.Union muss gewährleistet sein.
Sobald die Python-Sprache erweitert ist, müssen mypy [1] und andere Typ-Checker aktualisiert werden, um diese neue Syntax zu akzeptieren.
- Eine vorgeschlagene Implementierung für cpython finden Sie hier.
- Eine vorgeschlagene Implementierung für mypy finden Sie hier.
Referenzen
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-0604.rst
Zuletzt geändert: 2024-02-16 17:06:07 GMT