PEP 484 – Typenhinweise
- Autor:
- Guido van Rossum <guido at python.org>, Jukka Lehtosalo <jukka.lehtosalo at iki.fi>, Łukasz Langa <lukasz at python.org>
- BDFL-Delegate:
- Mark Shannon
- Discussions-To:
- Python-Dev Liste
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Typisierung
- Erstellt:
- 29-Sep-2014
- Python-Version:
- 3.5
- Post-History:
- 16-Jan-2015, 20-Mar-2015, 17-Apr-2015, 20-May-2015, 22-May-2015
- Resolution:
- Python-Dev Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Begründung und Ziele
- Die Bedeutung von Annotationen
- Typdefinitions-Syntax
- Akzeptable Typenhinweise
- Verwendung von None
- Typ-Aliase
- Callable
- Generics
- Benutzerdefinierte generische Typen
- Geltungsbereichsregeln für Typvariablen
- Instanziierung generischer Klassen und Typentfernung
- Beliebige generische Typen als Basisklassen
- Abstrakte generische Typen
- Typvariablen mit oberer Schranke
- Kovarianz und Kontravarianz
- Der Zahlenturm
- Vorwärtsdeklarationen
- Union-Typen
- Unterstützung für Singleton-Typen in Unions
- Der Typ
Any - Der Typ
NoReturn - Der Typ von Klassenobjekten
- Annotation von Instanz- und Klassenmethoden
- Versions- und Plattformprüfung
- Laufzeit- oder Typenprüfung?
- Beliebige Argumentlisten und Standardargumentwerte
- Nur positionsbezogene Argumente
- Annotation von Generatorfunktionen und Koroutinen
- Kompatibilität mit anderen Verwendungen von Funktionsannotationen
- Typ-Kommentare
- Casts
- NewType-Hilfsfunktion
- Stub-Dateien
- Ausnahmen
- Das Modul
typing - Vorgeschlagene Syntax für Python 2.7 und überlappenden Code
- Abgelehnte Alternativen
- PEP-Entwicklungsprozess
- Danksagungen
- Urheberrecht
Zusammenfassung
PEP 3107 führte Syntax für Funktionsannotationen ein, aber die Semantik wurde bewusst offen gelassen. Es gab inzwischen genügend externe Nutzung für statische Typanalyse, sodass die Community von einem Standardvokabular und Basistools innerhalb der Standardbibliothek profitieren würde.
Dieser PEP führt ein provisorisches Modul ein, um diese Standarddefinitionen und -werkzeuge bereitzustellen, zusammen mit einigen Konventionen für Situationen, in denen Annotationen nicht verfügbar sind.
Beachten Sie, dass dieser PEP weiterhin ausdrücklich ANDERE Verwendungen von Annotationen NICHT verbietet, noch (oder verbietet) irgendeine bestimmte Verarbeitung von Annotationen vorschreibt, selbst wenn diese dieser Spezifikation entsprechen. Er ermöglicht lediglich eine bessere Koordination, so wie PEP 333 für Web-Frameworks.
Zum Beispiel ist hier eine einfache Funktion, deren Argument- und Rückgabetyp in den Annotationen deklariert sind
def greeting(name: str) -> str:
return 'Hello ' + name
Während diese Annotationen zur Laufzeit über das übliche Attribut __annotations__ verfügbar sind, _findet keine Typenprüfung zur Laufzeit statt_. Stattdessen geht der Vorschlag von der Existenz eines separaten Offline-Typenprüfers aus, den Benutzer nach Belieben über ihren Quellcode laufen lassen können. Im Wesentlichen fungiert ein solcher Typenprüfer als sehr leistungsfähiger Linter. (Obwohl es für einzelne Benutzer natürlich möglich wäre, einen ähnlichen Prüfer zur Laufzeit für die Durchsetzung von Design By Contract oder JIT-Optimierungen einzusetzen, sind diese Werkzeuge noch nicht so ausgereift.)
Der Vorschlag ist stark von mypy inspiriert. Zum Beispiel kann der Typ „Sequenz von Ganzzahlen“ als Sequence[int] geschrieben werden. Die eckigen Klammern bedeuten, dass keine neue Syntax zur Sprache hinzugefügt werden muss. Das Beispiel hier verwendet einen benutzerdefinierten Typ Sequence, der aus einem reinen Python-Modul typing importiert wird. Die Notation Sequence[int] funktioniert zur Laufzeit, indem __getitem__() in der Metaklasse implementiert wird (aber ihre Bedeutung ist hauptsächlich für einen Offline-Typenprüfer).
Das Typsystem unterstützt Unions, generische Typen und einen speziellen Typ namens Any, der konsistent mit (d. h. zuweisbar von und zu) allen Typen ist. Dieses letztere Merkmal stammt aus der Idee des graduellen Typisierens. Graduelles Typisieren und das vollständige Typsystem werden in PEP 483 erklärt.
Andere Ansätze, von denen wir uns inspirieren ließen oder mit denen unser Ansatz verglichen und kontrastiert werden kann, werden in PEP 482 beschrieben.
Begründung und Ziele
PEP 3107 fügte Unterstützung für beliebige Annotationen zu Teilen einer Funktionsdefinition hinzu. Obwohl den Annotationen damals keine Bedeutung zugewiesen wurde, gab es immer ein implizites Ziel, sie für Typ-Hinting zu verwenden, was in diesem PEP als erster möglicher Anwendungsfall aufgeführt ist.
Dieser PEP zielt darauf ab, eine Standard-Syntax für Typ-Annotationen bereitzustellen, die Python-Code für einfachere statische Analyse und Refactoring, potenzielle Laufzeit-Typenprüfung und (vielleicht, in einigen Kontexten) Code-Generierung unter Nutzung von Typinformationen öffnet.
Von diesen Zielen ist die statische Analyse die wichtigste. Dazu gehört die Unterstützung für Offline-Typenprüfer wie mypy sowie die Bereitstellung einer Standardnotation, die von IDEs für Code-Vervollständigung und Refactoring verwendet werden kann.
Non-goals
Während das vorgeschlagene Typing-Modul einige Bausteine für die Laufzeit-Typenprüfung enthalten wird – insbesondere die Funktion get_type_hints() –, müssten Drittanbieterpakete entwickelt werden, um spezifische Laufzeit-Typenprüfungsfunktionen zu implementieren, beispielsweise unter Verwendung von Dekoratoren oder Metaklassen. Die Verwendung von Typ-Hinweisen zur Leistungsoptimierung bleibt als Übung für den Leser.
Es sollte auch betont werden, dass **Python eine dynamisch typisierte Sprache bleiben wird und die Autoren keinerlei Wunsch haben, Typ-Hinweise jemals obligatorisch zu machen, auch nicht per Konvention.**
Die Bedeutung von Annotationen
Jede Funktion ohne Annotationen sollte von jedem Typenprüfer als die allgemeinste mögliche Art behandelt oder ignoriert werden. Funktionen mit dem Dekorator @no_type_check sollten als ohne Annotationen behandelt werden.
Es wird empfohlen, aber nicht vorgeschrieben, dass geprüfte Funktionen Annotationen für alle Argumente und den Rückgabetyp haben. Für eine geprüfte Funktion ist die Standardannotation für Argumente und den Rückgabetyp Any. Eine Ausnahme bildet das erste Argument von Instanz- und Klassenmethoden. Wenn es nicht annotiert ist, wird angenommen, dass es den Typ der enthaltenden Klasse für Instanzmethoden und einen Typobjekttyp, der dem enthaltenden Klassenobjekt entspricht, für Klassenmethoden hat. Zum Beispiel hat in Klasse A das erste Argument einer Instanzmethode den impliziten Typ A. In einer Klassenmethode kann der genaue Typ des ersten Arguments mit der verfügbaren Typnotation nicht dargestellt werden.
(Beachten Sie, dass der Rückgabetyp von __init__ mit -> None annotiert werden sollte. Der Grund dafür ist subtil. Wenn __init__ eine Rückgabenotations von -> None annimmt, würde das bedeuten, dass eine argumentlose, unannotierte __init__-Methode immer noch typgeprüft werden sollte? Anstatt dies mehrdeutig zu lassen oder eine Ausnahme von der Ausnahme einzuführen, sagen wir einfach, dass __init__ eine Rückgabenotation haben sollte; das Standardverhalten ist somit dasselbe wie bei anderen Methoden.)
Ein Typenprüfer wird erwartet, den Körper einer geprüften Funktion auf Konsistenz mit den gegebenen Annotationen zu prüfen. Die Annotationen können auch verwendet werden, um die Korrektheit von Aufrufen zu prüfen, die in anderen geprüften Funktionen vorkommen.
Von Typenprüfern wird erwartet, dass sie versuchen, so viele Informationen wie nötig abzuleiten. Die Mindestanforderung ist, die eingebauten Dekoratoren @property, @staticmethod und @classmethod zu verarbeiten.
Typdefinitions-Syntax
Die Syntax nutzt Annotationen im PEP 3107-Stil mit einer Reihe von Erweiterungen, die in den folgenden Abschnitten beschrieben werden. In seiner Grundform wird Typ-Hinting verwendet, indem Funktionsannotations-Slots mit Klassen gefüllt werden
def greeting(name: str) -> str:
return 'Hello ' + name
Dies besagt, dass der erwartete Typ des Arguments name str ist. Analog ist der erwartete Rückgabetyp str.
Ausdrücke, deren Typ eine Unterklasse eines bestimmten Argumenttyps ist, werden ebenfalls für dieses Argument akzeptiert.
Akzeptable Typenhinweise
Typ-Hinweise können eingebaute Klassen sein (einschließlich derer, die in der Standardbibliothek oder in externen Erweiterungsmodulen definiert sind), abstrakte Basisklassen, Typen, die im Modul types verfügbar sind, und benutzerdefinierte Klassen (einschließlich derer, die in der Standardbibliothek oder in externen Modulen definiert sind).
Während Annotationen normalerweise das beste Format für Typ-Hinweise sind, gibt es Zeiten, in denen es angemessener ist, sie durch einen speziellen Kommentar darzustellen, oder in einer separat verteilten Stub-Datei. (Siehe unten für Beispiele.)
Annotationen müssen gültige Ausdrücke sein, die ohne Ausnahme ausgewertet werden, wenn die Funktion definiert wird (siehe unten für Vorwärtsdeklarationen).
Annotationen sollten einfach gehalten werden, da statische Analysewerkzeuge die Werte möglicherweise nicht interpretieren können. Dynamisch berechnete Typen werden wahrscheinlich nicht verstanden. (Dies ist eine absichtlich etwas vage Anforderung, spezifische Ein- und Ausschlüsse können in zukünftigen Versionen dieses PEP nach Bedarf hinzugefügt werden, basierend auf der Diskussion.)
Zusätzlich zu den oben genannten können die folgenden unten definierten speziellen Konstrukte verwendet werden: None, Any, Union, Tuple, Callable, alle ABCs und Platzhalter für konkrete Klassen, die aus typing exportiert werden (z. B. Sequence und Dict), Typvariablen und Typ-Aliase.
Alle neu eingeführten Namen, die zur Unterstützung der in den folgenden Abschnitten beschriebenen Funktionen verwendet werden (wie Any und Union), sind im Modul typing verfügbar.
Verwendung von None
Wenn None in einem Typ-Hinweis verwendet wird, gilt es als äquivalent zu type(None).
Typ-Aliase
Typ-Aliase werden durch einfache Variablendeklarationen definiert
Url = str
def retry(url: Url, retry_count: int) -> None: ...
Beachten Sie, dass wir empfehlen, Aliasnamen großzuschreiben, da sie benutzerdefinierte Typen darstellen, die (wie benutzerdefinierte Klassen) typischerweise so benannt werden.
Typ-Aliase können so komplex sein wie Typ-Hinweise in Annotationen – alles, was als Typ-Hinweis akzeptabel ist, ist auch in einem Typ-Alias akzeptabel
from typing import TypeVar, Iterable, Tuple
T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]
def inproduct(v: Vector[T]) -> T:
return sum(x*y for x, y in v)
def dilate(v: Vector[T], scale: T) -> Vector[T]:
return ((x * scale, y * scale) for x, y in v)
vec = [] # type: Vector[float]
Dies ist äquivalent zu
from typing import TypeVar, Iterable, Tuple
T = TypeVar('T', int, float, complex)
def inproduct(v: Iterable[Tuple[T, T]]) -> T:
return sum(x*y for x, y in v)
def dilate(v: Iterable[Tuple[T, T]], scale: T) -> Iterable[Tuple[T, T]]:
return ((x * scale, y * scale) for x, y in v)
vec = [] # type: Iterable[Tuple[float, float]]
Callable
Frameworks, die Callback-Funktionen mit bestimmten Signaturen erwarten, können mit Callable[[Arg1Type, Arg2Type], ReturnType] typ-hinted werden. Beispiele
from typing import Callable
def feeder(get_next_item: Callable[[], str]) -> None:
# Body
def async_query(on_success: Callable[[int], None],
on_error: Callable[[int, Exception], None]) -> None:
# Body
Es ist möglich, den Rückgabetyp eines Callables zu deklarieren, ohne die Aufrufsignatur anzugeben, indem eine literale Ellipse (drei Punkte) für die Argumentliste eingesetzt wird
def partial(func: Callable[..., str], *args) -> Callable[..., str]:
# Body
Beachten Sie, dass keine eckigen Klammern um die Ellipse stehen. Die Argumente des Callbacks sind in diesem Fall vollständig uneingeschränkt (und Schlüsselwortargumente sind zulässig).
Da die Verwendung von Callbacks mit Schlüsselwortargumenten nicht als üblicher Anwendungsfall angesehen wird, gibt es derzeit keine Unterstützung für die Angabe von Schlüsselwortargumenten mit Callable. Ebenso gibt es keine Unterstützung für die Angabe von Callback-Signaturen mit einer variablen Anzahl von Argumenten eines bestimmten Typs.
Da typing.Callable eine Doppelrolle als Ersatz für collections.abc.Callable spielt, wird isinstance(x, typing.Callable) implementiert, indem auf isinstance(x, collections.abc.Callable) zurückgegriffen wird. isinstance(x, typing.Callable[...]) wird jedoch nicht unterstützt.
Generics
Da Typinformationen über Objekte, die in Containern gespeichert sind, nicht generell statisch abgeleitet werden können, wurden abstrakte Basisklassen erweitert, um die Subskription zu unterstützen, um erwartete Typen für Container-Elemente anzugeben. Beispiel
from typing import Mapping, Set
def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None: ...
Generics können parametrisiert werden, indem eine neue Factory in typing namens TypeVar verwendet wird. Beispiel
from typing import Sequence, TypeVar
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function
return l[0]
In diesem Fall ist der Vertrag, dass der zurückgegebene Wert mit den Elementen der Sammlung konsistent ist.
Ein TypeVar()-Ausdruck muss immer direkt einer Variablen zugewiesen werden (er sollte nicht Teil eines größeren Ausdrucks sein). Das Argument für TypeVar() muss ein String sein, der mit dem Variablennamen übereinstimmt, dem er zugewiesen wird. Typvariablen dürfen nicht neu definiert werden.
TypeVar unterstützt die Beschränkung parametrischer Typen auf eine feste Menge möglicher Typen (Hinweis: Diese Typen können nicht durch Typvariablen parametrisiert werden). Zum Beispiel können wir eine Typvariable definieren, die sich nur über str und bytes erstreckt. Standardmäßig erstreckt sich eine Typvariable über alle möglichen Typen. Beispiel für die Beschränkung einer Typvariable
from typing import TypeVar, Text
AnyStr = TypeVar('AnyStr', Text, bytes)
def concat(x: AnyStr, y: AnyStr) -> AnyStr:
return x + y
Die Funktion concat kann entweder mit zwei str-Argumenten oder zwei bytes-Argumenten aufgerufen werden, aber nicht mit einer Mischung aus str- und bytes-Argumenten.
Es sollten mindestens zwei Beschränkungen vorhanden sein, falls überhaupt; die Angabe einer einzelnen Beschränkung ist nicht zulässig.
Unterklassen von Typen, die durch eine Typvariable beschränkt sind, sollten im Kontext der Typvariable als ihre jeweiligen explizit aufgeführten Basistypen behandelt werden. Betrachten Sie dieses Beispiel
class MyStr(str): ...
x = concat(MyStr('apple'), MyStr('pie'))
Der Aufruf ist gültig, aber die Typvariable AnyStr wird auf str und nicht auf MyStr gesetzt. Tatsächlich wird der abgeleitete Typ des Rückgabewerts, der x zugewiesen wird, ebenfalls str sein.
Zusätzlich ist Any ein gültiger Wert für jede Typvariable. Betrachten Sie Folgendes
def count_truthy(elements: List[Any]) -> int:
return sum(1 for elem in elements if elem)
Dies ist äquivalent zum Weglassen der generischen Notation und dem einfachen Sagen von elements: List.
Benutzerdefinierte generische Typen
Sie können eine Basisklasse Generic einbeziehen, um eine benutzerdefinierte Klasse als generisch zu definieren. Beispiel
from typing import TypeVar, Generic
from logging import Logger
T = TypeVar('T')
class LoggedVar(Generic[T]):
def __init__(self, value: T, name: str, logger: Logger) -> None:
self.name = name
self.logger = logger
self.value = value
def set(self, new: T) -> None:
self.log('Set ' + repr(self.value))
self.value = new
def get(self) -> T:
self.log('Get ' + repr(self.value))
return self.value
def log(self, message: str) -> None:
self.logger.info('{}: {}'.format(self.name, message))
Generic[T] als Basisklasse definiert, dass die Klasse LoggedVar einen einzigen Typparameter T akzeptiert. Dies macht T auch als Typ innerhalb des Klassenkörpers gültig.
Die Basisklasse Generic verwendet eine Metaklasse, die __getitem__ definiert, sodass LoggedVar[t] als Typ gültig ist
from typing import Iterable
def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
for var in vars:
var.set(0)
Ein generischer Typ kann beliebig viele Typvariablen haben, und Typvariablen können beschränkt sein. Dies ist gültig
from typing import TypeVar, Generic
...
T = TypeVar('T')
S = TypeVar('S')
class Pair(Generic[T, S]):
...
Jedes Typvariablenargument für Generic muss eindeutig sein. Dies ist daher ungültig
from typing import TypeVar, Generic
...
T = TypeVar('T')
class Pair(Generic[T, T]): # INVALID
...
Die Basisklasse Generic[T] ist in einfachen Fällen redundant, in denen Sie eine andere generische Klasse unterklassifizieren und Typvariablen für ihre Parameter angeben
from typing import TypeVar, Iterator
T = TypeVar('T')
class MyIter(Iterator[T]):
...
Diese Klassendefinition ist äquivalent zu
class MyIter(Iterator[T], Generic[T]):
...
Sie können Mehrfachvererbung mit Generic verwenden
from typing import TypeVar, Generic, Sized, Iterable, Container, Tuple
T = TypeVar('T')
class LinkedList(Sized, Generic[T]):
...
K = TypeVar('K')
V = TypeVar('V')
class MyMapping(Iterable[Tuple[K, V]],
Container[Tuple[K, V]],
Generic[K, V]):
...
Das Unterklassifizieren einer generischen Klasse ohne Angabe von Typparametern nimmt Any für jede Position an. Im folgenden Beispiel ist MyIterable nicht generisch, erbt aber implizit von Iterable[Any]
from typing import Iterable
class MyIterable(Iterable): # Same as Iterable[Any]
...
Generische Metaklassen werden nicht unterstützt.
Geltungsbereichsregeln für Typvariablen
Typvariablen folgen normalen Namensauflösungsregeln. Im Kontext der statischen Typenprüfung gibt es jedoch einige Sonderfälle
- Eine in einer generischen Funktion verwendete Typvariable kann im selben Codeblock als unterschiedliche Typen interpretiert werden. Beispiel
from typing import TypeVar, Generic T = TypeVar('T') def fun_1(x: T) -> T: ... # T here def fun_2(x: T) -> T: ... # and here could be different fun_1(1) # This is OK, T is inferred to be int fun_2('a') # This is also OK, now T is str
- Eine in einer Methode einer generischen Klasse verwendete Typvariable, die mit einer der Variablen übereinstimmt, die diese Klasse parametrisieren, ist immer an diese Variable gebunden. Beispiel
from typing import TypeVar, Generic T = TypeVar('T') class MyClass(Generic[T]): def meth_1(self, x: T) -> T: ... # T here def meth_2(self, x: T) -> T: ... # and here are always the same a = MyClass() # type: MyClass[int] a.meth_1(1) # OK a.meth_2('a') # This is an error!
- Eine in einer Methode verwendete Typvariable, die nicht mit einer der Variablen übereinstimmt, die die Klasse parametrisieren, macht diese Methode zu einer generischen Funktion für diese Variable
T = TypeVar('T') S = TypeVar('S') class Foo(Generic[T]): def method(self, x: T, y: S) -> S: ... x = Foo() # type: Foo[int] y = x.method(0, "abc") # inferred type of y is str
- Ungebundene Typvariablen sollten nicht in den Körpern von generischen Funktionen oder in Klassenkörpern außerhalb von Methodendefinitionen vorkommen
T = TypeVar('T') S = TypeVar('S') def a_fun(x: T) -> None: # this is OK y = [] # type: List[T] # but below is an error! y = [] # type: List[S] class Bar(Generic[T]): # this is also an error an_attr = [] # type: List[S] def do_something(x: S) -> S: # this is OK though ...
- Eine generische Klassendefinition, die innerhalb einer generischen Funktion vorkommt, sollte keine Typvariablen verwenden, die die generische Funktion parametrisieren
from typing import List def a_fun(x: T) -> None: # This is OK a_list = [] # type: List[T] ... # This is however illegal class MyGeneric(Generic[T]): ...
- Eine generische Klasse, die in einer anderen generischen Klasse verschachtelt ist, kann nicht dieselben Typvariablen verwenden. Der Geltungsbereich der Typvariablen der äußeren Klasse deckt die innere nicht ab
T = TypeVar('T') S = TypeVar('S') class Outer(Generic[T]): class Bad(Iterable[T]): # Error ... class AlsoBad: x = None # type: List[T] # Also an error class Inner(Iterable[S]): # OK ... attr = None # type: Inner[T] # Also OK
Instanziierung generischer Klassen und Typentfernung
Benutzerdefinierte generische Klassen können instanziiert werden. Angenommen, wir schreiben eine Node-Klasse, die von Generic[T] erbt
from typing import TypeVar, Generic
T = TypeVar('T')
class Node(Generic[T]):
...
Um Node-Instanzen zu erstellen, rufen Sie Node() auf, genau wie bei einer regulären Klasse. Zur Laufzeit ist der Typ (Klasse) der Instanz Node. Aber welchen Typ hat sie für den Typenprüfer? Die Antwort hängt davon ab, wie viele Informationen im Aufruf verfügbar sind. Wenn der Konstruktor (__init__ oder __new__) T in seiner Signatur verwendet und ein entsprechendes Argument übergeben wird, wird der Typ des entsprechenden Arguments/der entsprechenden Argumente substituiert. Andernfalls wird Any angenommen. Beispiel
from typing import TypeVar, Generic
T = TypeVar('T')
class Node(Generic[T]):
x = None # type: T # Instance attribute (see below)
def __init__(self, label: T = None) -> None:
...
x = Node('') # Inferred type is Node[str]
y = Node(0) # Inferred type is Node[int]
z = Node() # Inferred type is Node[Any]
Falls der abgeleitete Typ [Any] verwendet, der beabsichtigte Typ jedoch spezifischer ist, können Sie einen Typ-Kommentar (siehe unten) verwenden, um den Typ der Variablen zu erzwingen, z.B.
# (continued from previous example)
a = Node() # type: Node[int]
b = Node() # type: Node[str]
Alternativ können Sie einen spezifischen konkreten Typ instanziieren, z.B.
# (continued from previous example)
p = Node[int]()
q = Node[str]()
r = Node[int]('') # Error
s = Node[str](0) # Error
Beachten Sie, dass der Laufzeittyp (Klasse) von p und q immer noch nur Node ist – Node[int] und Node[str] sind unterscheidbare Klassenobjekte, aber die Laufzeitklasse der durch ihre Instanziierung erstellten Objekte speichert die Unterscheidung nicht. Dieses Verhalten wird als „Typentfernung“ bezeichnet; es ist üblich in Sprachen mit Generics (z.B. Java, TypeScript).
Die Verwendung von generischen Klassen (parametrisiert oder nicht) zum Zugriff auf Attribute führt zu einem Fehler bei der Typenprüfung. Außerhalb des Klassendefinitionskörpers kann ein Klassenattribut nicht zugewiesen werden und kann nur durch den Zugriff über eine Klasseninstanz abgerufen werden, die kein Instanzattribut mit demselben Namen hat
# (continued from previous example)
Node[int].x = 1 # Error
Node[int].x # Error
Node.x = 1 # Error
Node.x # Error
type(p).x # Error
p.x # Ok (evaluates to None)
Node[int]().x # Ok (evaluates to None)
p.x = 1 # Ok, but assigning to instance attribute
Generische Versionen von abstrakten Sammlungen wie Mapping oder Sequence und generische Versionen von eingebauten Klassen – List, Dict, Set und FrozenSet – können nicht instanziiert werden. Konkrete benutzerdefinierte Unterklassen davon und generische Versionen konkreter Sammlungen können jedoch instanziiert werden
data = DefaultDict[int, bytes]()
Beachten Sie, dass man statische Typen und Laufzeitklassen nicht verwechseln sollte. Der Typ wird auch in diesem Fall entfernt und der obige Ausdruck ist nur eine Abkürzung für
data = collections.defaultdict() # type: DefaultDict[int, bytes]
Es wird nicht empfohlen, die indizierte Klasse (z. B. Node[int]) direkt in einem Ausdruck zu verwenden – die Verwendung eines Typ-Alias (z. B. IntNode = Node[int]) ist stattdessen vorzuziehen. (Erstens hat das Erstellen der indizierten Klasse, z. B. Node[int], Laufzeitkosten. Zweitens ist die Verwendung eines Typ-Alias lesbarer.)
Beliebige generische Typen als Basisklassen
Generic[T] ist nur als Basisklasse gültig – es ist kein richtiger Typ. Benutzerdefinierte generische Typen wie LinkedList[T] aus dem obigen Beispiel und eingebaute generische Typen und ABCs wie List[T] und Iterable[T] sind jedoch sowohl als Typen als auch als Basisklassen gültig. Zum Beispiel können wir eine Unterklasse von Dict definieren, die Typargumente spezialisiert
from typing import Dict, List, Optional
class Node:
...
class SymbolTable(Dict[str, List[Node]]):
def push(self, name: str, node: Node) -> None:
self.setdefault(name, []).append(node)
def pop(self, name: str) -> Node:
return self[name].pop()
def lookup(self, name: str) -> Optional[Node]:
nodes = self.get(name)
if nodes:
return nodes[-1]
return None
SymbolTable ist eine Unterklasse von dict und eine Unterart von Dict[str, List[Node]].
Wenn eine generische Basisklasse eine Typvariable als Typargument hat, macht dies die definierte Klasse generisch. Zum Beispiel können wir eine generische LinkedList-Klasse definieren, die iterierbar und ein Container ist
from typing import TypeVar, Iterable, Container
T = TypeVar('T')
class LinkedList(Iterable[T], Container[T]):
...
Nun ist LinkedList[int] ein gültiger Typ. Beachten Sie, dass wir T mehrfach in der Basisklassenliste verwenden können, solange wir nicht dieselbe Typvariable T mehrfach innerhalb von Generic[...] verwenden.
Betrachten Sie auch das folgende Beispiel
from typing import TypeVar, Mapping
T = TypeVar('T')
class MyDict(Mapping[str, T]):
...
In diesem Fall hat MyDict einen einzelnen Parameter, T.
Abstrakte generische Typen
Die von Generic verwendete Metaklasse ist eine Unterklasse von abc.ABCMeta. Eine generische Klasse kann eine ABC sein, indem sie abstrakte Methoden oder Eigenschaften enthält, und generische Klassen können auch ABCs als Basisklassen ohne Metaklassenkonflikt haben.
Typvariablen mit oberer Schranke
Eine Typvariable kann eine obere Schranke mit bound=<type> angeben (Hinweis: <type> kann selbst nicht durch Typvariablen parametrisiert werden). Dies bedeutet, dass ein tatsächlicher Typ, der für die Typvariable substituiert wird (explizit oder implizit), eine Unterklasse des Schrankentyps sein muss. Beispiel
from typing import TypeVar, Sized
ST = TypeVar('ST', bound=Sized)
def longer(x: ST, y: ST) -> ST:
if len(x) > len(y):
return x
else:
return y
longer([1], [1, 2]) # ok, return type List[int]
longer({1}, {1, 2}) # ok, return type Set[int]
longer([1], {1, 2}) # ok, return type Collection[int]
Eine obere Schranke kann nicht mit Typbeschränkungen kombiniert werden (wie bei AnyStr, siehe Beispiel oben); Typbeschränkungen führen dazu, dass der abgeleitete Typ _genau_ einer der Beschränkungstypen ist, während eine obere Schranke nur verlangt, dass der tatsächliche Typ eine Unterklasse des Schrankentyps ist.
Kovarianz und Kontravarianz
Betrachten Sie eine Klasse Employee mit einer Unterklasse Manager. Angenommen, wir haben jetzt eine Funktion mit einem Argument, das mit List[Employee] annotiert ist. Sollten wir in der Lage sein, diese Funktion mit einer Variablen vom Typ List[Manager] als Argument aufzurufen? Viele Leute würden „ja, natürlich“ antworten, ohne die Konsequenzen zu bedenken. Aber ohne mehr über die Funktion zu wissen, sollte ein Typenprüfer einen solchen Aufruf ablehnen: Die Funktion könnte eine Employee-Instanz zur Liste hinzufügen, was den Typ der Variablen im Aufrufer verletzen würde.
Es stellt sich heraus, dass ein solches Argument _kontravariant_ wirkt, während die intuitive Antwort (die korrekt ist, wenn die Funktion ihr Argument nicht mutiert!) verlangt, dass das Argument _kovariant_ wirkt. Eine längere Einführung in diese Konzepte finden Sie auf Wikipedia und in PEP 483; hier zeigen wir nur, wie das Verhalten eines Typenprüfers gesteuert wird.
Standardmäßig werden generische Typen für alle Typvariablen als _invariant_ betrachtet, was bedeutet, dass Werte für Variablen, die mit Typen wie List[Employee] annotiert sind, exakt mit der Typannotation übereinstimmen müssen – keine Unterklassen oder Oberklassen des Typparameters (in diesem Beispiel Employee) sind erlaubt.
Um die Deklaration von Containertypen zu erleichtern, bei denen kovariante oder kontravariante Typenprüfung akzeptabel ist, akzeptieren Typvariablen Schlüsselwortargumente covariant=True oder contravariant=True. Höchstens eines davon kann übergeben werden. Mit solchen Variablen definierte generische Typen gelten als kovariant oder kontravariant in der entsprechenden Variable. Konventionell wird empfohlen, Namen, die auf _co enden, für mit covariant=True definierte Typvariablen und Namen, die auf _contra enden, für mit contravariant=True definierte zu verwenden.
Ein typisches Beispiel beinhaltet die Definition einer unveränderlichen (oder schreibgeschützten) Containerklasse
from typing import TypeVar, Generic, Iterable, Iterator
T_co = TypeVar('T_co', covariant=True)
class ImmutableList(Generic[T_co]):
def __init__(self, items: Iterable[T_co]) -> None: ...
def __iter__(self) -> Iterator[T_co]: ...
...
class Employee: ...
class Manager(Employee): ...
def dump_employees(emps: ImmutableList[Employee]) -> None:
for emp in emps:
...
mgrs = ImmutableList([Manager()]) # type: ImmutableList[Manager]
dump_employees(mgrs) # OK
Die schreibgeschützten Collection-Klassen in typing sind alle kovariant in ihrer Typparameter deklariert (z. B. Mapping und Sequence). Die mutablen Collection-Klassen (z. B. MutableMapping und MutableSequence) sind invariant deklariert. Das einzige Beispiel für einen kontravarianten Typ ist der Generator-Typ, der kontravariant im send()-Argumenttyp ist (siehe unten).
Hinweis: Kovarianz oder Kontravarianz ist keine Eigenschaft einer Typparameter, sondern eine Eigenschaft einer generischen Klasse, die mit dieser Variablen definiert wurde. Varianz ist nur auf generische Typen anwendbar; generische Funktionen haben diese Eigenschaft nicht. Letztere sollten nur mit Typparametern ohne die Schlüsselwortargumente covariant oder contravariant definiert werden. Zum Beispiel ist das folgende Beispiel in Ordnung
from typing import TypeVar
class Employee: ...
class Manager(Employee): ...
E = TypeVar('E', bound=Employee)
def dump_employee(e: E) -> None: ...
dump_employee(Manager()) # OK
während das folgende verboten ist
B_co = TypeVar('B_co', covariant=True)
def bad_func(x: B_co) -> B_co: # Flagged as error by a type checker
...
Der Zahlenturm
PEP 3141 definiert den numerischen Turm von Python, und das stdlib-Modul numbers implementiert die entsprechenden ABCs (Number, Complex, Real, Rational und Integral). Es gibt einige Probleme mit diesen ABCs, aber die eingebauten konkreten numerischen Klassen complex, float und int sind allgegenwärtig (besonders die beiden letzteren :-).
Anstatt zu verlangen, dass Benutzer import numbers schreiben und dann numbers.Float usw. verwenden, schlägt diese PEP eine einfache Abkürzung vor, die fast genauso effektiv ist: Wenn ein Argument mit dem Typ float annotiert ist, ist ein Argument vom Typ int akzeptabel; ähnlich sind für ein Argument, das mit complex annotiert ist, Argumente vom Typ float oder int akzeptabel. Dies behandelt keine Klassen, die die entsprechenden ABCs implementieren, oder die Klasse fractions.Fraction, aber wir glauben, dass diese Anwendungsfälle äußerst selten sind.
Vorwärtsdeklarationen
Wenn ein Typ-Hint Namen enthält, die noch nicht definiert wurden, kann diese Definition als Zeichenkettenliteral ausgedrückt werden, um später aufgelöst zu werden.
Eine Situation, in der dies häufig vorkommt, ist die Definition einer Container-Klasse, wobei die zu definierende Klasse in der Signatur einiger Methoden vorkommt. Zum Beispiel funktioniert der folgende Code (der Anfang einer einfachen Binärbaum-Implementierung) nicht
class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right
Um dies zu beheben, schreiben wir
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
Das Zeichenkettenliteral sollte einen gültigen Python-Ausdruck enthalten (d. h. compile(lit, '', 'eval') sollte ein gültiges Code-Objekt sein) und es sollte ohne Fehler ausgewertet werden, sobald das Modul vollständig geladen wurde. Der lokale und globale Namensraum, in dem es ausgewertet wird, sollte derselbe Namensraum sein, in dem Standardargumente derselben Funktion ausgewertet würden.
Darüber hinaus sollte der Ausdruck als gültiger Typ-Hint analysierbar sein, d. h. er unterliegt den Regeln aus dem Abschnitt Akzeptable Typ-Hints oben.
Es ist zulässig, Zeichenkettenliterale als Teil eines Typ-Hints zu verwenden, zum Beispiel
class Tree:
...
def leaves(self) -> List['Tree']:
...
Ein häufiger Anwendungsfall für Vorwärtsreferenzen ist, wenn z. B. Django-Modelle in Signaturen benötigt werden. Typischerweise ist jedes Modell in einer separaten Datei und hat Methoden, die Argumente annehmen, deren Typ andere Modelle umfasst. Aufgrund der Art und Weise, wie zirkuläre Importe in Python funktionieren, ist es oft nicht möglich, alle benötigten Modelle direkt zu importieren
# File models/a.py
from models.b import B
class A(Model):
def foo(self, b: B): ...
# File models/b.py
from models.a import A
class B(Model):
def bar(self, a: A): ...
# File main.py
from models.a import A
from models.b import B
Unter der Annahme, dass main zuerst importiert wird, schlägt dies mit einem ImportError in der Zeile from models.a import A in models/b.py fehl, das aus models/a.py importiert wird, bevor a die Klasse A definiert hat. Die Lösung besteht darin, zu Modul-only-Imports zu wechseln und die Modelle anhand ihres Namens _modul_._klasse_ zu referenzieren
# File models/a.py
from models import b
class A(Model):
def foo(self, b: 'b.B'): ...
# File models/b.py
from models import a
class B(Model):
def bar(self, a: 'a.A'): ...
# File main.py
from models.a import A
from models.b import B
Union-Typen
Da die Akzeptanz einer kleinen, begrenzten Menge erwarteter Typen für ein einzelnes Argument üblich ist, gibt es eine neue spezielle Fabrik namens Union. Beispiel
from typing import Union
def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
if isinstance(e, Employee):
e = [e]
...
Ein von Union[T1, T2, ...] gefakter Typ ist eine Obermenge aller Typen T1, T2 usw., so dass ein Wert, der ein Element eines dieser Typen ist, für ein Argument akzeptabel ist, das mit Union[T1, T2, ...] annotiert ist.
Ein häufiger Fall von Union-Typen sind optionale Typen. Standardmäßig ist None kein gültiger Wert für jeden Typ, es sei denn, ein Standardwert von None wurde in der Funktionsdefinition bereitgestellt. Beispiele
def handle_employee(e: Union[Employee, None]) -> None: ...
Als Abkürzung für Union[T1, None] können Sie Optional[T1] schreiben; zum Beispiel ist das obige äquivalent zu
from typing import Optional
def handle_employee(e: Optional[Employee]) -> None: ...
Eine frühere Version dieser PEP erlaubte Typ-Prüfern, einen optionalen Typ anzunehmen, wenn der Standardwert None ist, wie in diesem Code
def handle_employee(e: Employee = None): ...
Dies wäre als äquivalent zu behandelt worden
def handle_employee(e: Optional[Employee] = None) -> None: ...
Dies ist nicht mehr das empfohlene Verhalten. Typ-Prüfer sollten dazu übergehen, die explizite Angabe des optionalen Typs zu verlangen.
Unterstützung für Singleton-Typen in Unions
Eine Singleton-Instanz wird häufig verwendet, um einen speziellen Zustand zu markieren, insbesondere in Situationen, in denen None ebenfalls ein gültiger Wert für eine Variable ist. Beispiel
_empty = object()
def func(x=_empty):
if x is _empty: # default argument value
return 0
elif x is None: # argument was provided and it's None
return 1
else:
return x * 2
Um präzise Typen in solchen Situationen zu ermöglichen, sollte der Benutzer den Union-Typ in Verbindung mit der Klasse enum.Enum aus der Standardbibliothek verwenden, damit Typfehler statisch abgefangen werden können
from typing import Union
from enum import Enum
class Empty(Enum):
token = 0
_empty = Empty.token
def func(x: Union[int, None, Empty] = _empty) -> int:
boom = x * 42 # This fails type check
if x is _empty:
return 0
elif x is None:
return 1
else: # At this point typechecker knows that x can only have type int
return x * 2
Da die Unterklassen von Enum nicht weiter unterklassifiziert werden können, kann der Typ der Variablen x in allen Zweigen des obigen Beispiels statisch abgeleitet werden. Der gleiche Ansatz ist anwendbar, wenn mehr als ein Singleton-Objekt benötigt wird: Man kann eine Aufzählung verwenden, die mehr als einen Wert hat
class Reason(Enum):
timeout = 1
error = 2
def process(response: Union[str, Reason] = '') -> str:
if response is Reason.timeout:
return 'TIMEOUT'
elif response is Reason.error:
return 'ERROR'
else:
# response can be only str, all other possible values exhausted
return 'PROCESSED: ' + response
Der Typ Any
Eine spezielle Art von Typ ist Any. Jeder Typ ist konsistent mit Any. Er kann als ein Typ betrachtet werden, der alle Werte und alle Methoden hat. Beachten Sie, dass Any und der eingebaute Typ object völlig unterschiedlich sind.
Wenn der Typ eines Werts object ist, lehnt der Typ-Prüfer fast alle Operationen darauf ab, und die Zuweisung zu einer Variablen (oder die Verwendung als Rückgabewert) eines spezialisierteren Typs ist ein Typfehler. Auf der anderen Seite, wenn ein Wert den Typ Any hat, erlaubt der Typ-Prüfer alle Operationen darauf, und ein Wert vom Typ Any kann einer Variablen (oder als Rückgabewert) eines eingeschränkteren Typs zugewiesen werden.
Ein Funktionsparameter ohne Annotation wird als mit Any annotiert angenommen. Wenn ein generischer Typ ohne Angabe von Typparametern verwendet wird, werden diese als Any angenommen
from typing import Mapping
def use_map(m: Mapping) -> None: # Same as Mapping[Any, Any]
...
Diese Regel gilt auch für Tuple, im Annotationskontext ist er äquivalent zu Tuple[Any, ...] und wiederum zu tuple. Ebenso ist ein bloßer Callable in einer Annotation äquivalent zu Callable[..., Any] und wiederum zu collections.abc.Callable
from typing import Tuple, List, Callable
def check_args(args: Tuple) -> bool:
...
check_args(()) # OK
check_args((42, 'abc')) # Also OK
check_args(3.14) # Flagged as error by a type checker
# A list of arbitrary callables is accepted by this function
def apply_callbacks(cbs: List[Callable]) -> None:
...
Der Typ NoReturn
Das Modul typing stellt einen speziellen Typ NoReturn bereit, um Funktionen zu annotieren, die niemals normal zurückkehren. Zum Beispiel eine Funktion, die bedingungslos eine Ausnahme auslöst
from typing import NoReturn
def stop() -> NoReturn:
raise RuntimeError('no way')
Die Annotation NoReturn wird für Funktionen wie sys.exit verwendet. Statische Typ-Prüfer stellen sicher, dass Funktionen, die als NoReturn annotiert sind, niemals wirklich zurückkehren, weder implizit noch explizit
import sys
from typing import NoReturn
def f(x: int) -> NoReturn: # Error, f(0) implicitly returns None
if x != 0:
sys.exit(1)
Die Prüfer erkennen auch, dass der Code nach Aufrufen solcher Funktionen nicht erreichbar ist und verhalten sich entsprechend
# continue from first example
def g(x: int) -> int:
if x > 0:
return x
stop()
return 'whatever works' # Error might be not reported by some checkers
# that ignore errors in unreachable blocks
Der Typ NoReturn ist nur als Rückgabeannotation von Funktionen gültig und wird als Fehler betrachtet, wenn er an anderer Stelle vorkommt
from typing import List, NoReturn
# All of the following are errors
def bad1(x: NoReturn) -> int:
...
bad2 = None # type: NoReturn
def bad3() -> List[NoReturn]:
...
Der Typ von Klassenobjekten
Manchmal möchten Sie über Klassenobjekte sprechen, insbesondere über Klassenobjekte, die von einer gegebenen Klasse erben. Dies kann als Type[C] geschrieben werden, wobei C eine Klasse ist. Zur Klarstellung: Während C (wenn als Annotation verwendet) sich auf Instanzen der Klasse C bezieht, bezieht sich Type[C] auf Unterklassen von C. (Dies ist eine ähnliche Unterscheidung wie zwischen object und type.)
Zum Beispiel, nehmen wir an, wir haben die folgenden Klassen
class User: ... # Abstract base for User classes
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...
Und nehmen wir an, wir haben eine Funktion, die eine Instanz einer dieser Klassen erstellt, wenn man ihr ein Klassenobjekt übergibt
def new_user(user_class):
user = user_class()
# (Here we could write the user object to a database)
return user
Ohne Type[] wäre das Beste, was wir tun könnten, um new_user() zu annotieren
def new_user(user_class: type) -> User:
...
Mit Type[] und einem Typvariablen mit oberer Schranke können wir jedoch viel mehr tun
U = TypeVar('U', bound=User)
def new_user(user_class: Type[U]) -> U:
...
Wenn wir nun new_user() mit einer spezifischen Unterklasse von User aufrufen, wird ein Typ-Prüfer den korrekten Typ des Ergebnisses ableiten
joe = new_user(BasicUser) # Inferred type is BasicUser
Der Wert, der Type[C] entspricht, muss ein tatsächliches Klassenobjekt sein, das eine Unterklasse von C ist, keine spezielle Form. Mit anderen Worten, im obigen Beispiel wird der Aufruf von z. B. new_user(Union[BasicUser, ProUser]) vom Typ-Prüfer abgelehnt (zusätzlich zum Fehlschlagen zur Laufzeit, da man keine Union instanziieren kann).
Beachten Sie, dass es zulässig ist, eine Union von Klassen als Parameter für Type[] zu verwenden, wie in
def new_non_team_user(user_class: Type[Union[BasicUser, ProUser]]):
user = new_user(user_class)
...
Das tatsächlich zur Laufzeit übergebene Argument muss jedoch immer noch ein konkretes Klassenobjekt sein, z. B. im obigen Beispiel
new_non_team_user(ProUser) # OK
new_non_team_user(TeamUser) # Disallowed by type checker
Type[Any] wird ebenfalls unterstützt (siehe unten für seine Bedeutung).
Type[T], wobei T eine Typvariable ist, ist erlaubt, wenn das erste Argument einer Klassenmethode annotiert wird (siehe relevanter Abschnitt).
Andere spezielle Konstrukte wie Tuple oder Callable sind als Argument für Type nicht zulässig.
Es gibt einige Bedenken hinsichtlich dieser Funktion: Wenn beispielsweise new_user() user_class() aufruft, impliziert dies, dass alle Unterklassen von User dies in ihrer Konstruktorsignatur unterstützen müssen. Dies ist jedoch nicht einzigartig für Type[]: Klassenmethoden haben ähnliche Bedenken. Ein Typ-Prüfer sollte Verstöße gegen solche Annahmen kennzeichnen, aber standardmäßig sollten Konstruktoraufrufe, die der Konstruktorsignatur in der angegebenen Basisklasse (User im obigen Beispiel) entsprechen, erlaubt sein. Ein Programm, das eine komplexe oder erweiterbare Klassenhierarchie enthält, könnte dies auch durch die Verwendung einer Factory-Klassenmethode handhaben. Eine zukünftige Überarbeitung dieser PEP kann bessere Möglichkeiten zur Behandlung dieser Bedenken einführen.
Wenn Type parametrisiert wird, erfordert es genau einen Parameter. Plain Type ohne Klammern ist äquivalent zu Type[Any] und dies wiederum zu type (die Wurzel der Metaklassen-Hierarchie von Python). Diese Äquivalenz motiviert auch den Namen Type im Gegensatz zu Alternativen wie Class oder SubType, die während der Diskussion dieser Funktion vorgeschlagen wurden; dies ähnelt der Beziehung zwischen z. B. List und list.
Bezüglich des Verhaltens von Type[Any] (oder Type oder type), bietet der Zugriff auf Attribute einer Variablen mit diesem Typ nur Attribute und Methoden, die von type definiert wurden (z. B. __repr__() und __mro__). Eine solche Variable kann mit beliebigen Argumenten aufgerufen werden, und der Rückgabetyp ist Any.
Type ist kovariant in seinem Parameter, da Type[Derived] eine Unterklasse von Type[Base] ist
def new_pro_user(pro_user_class: Type[ProUser]):
user = new_user(pro_user_class) # OK
...
Annotation von Instanz- und Klassenmethoden
In den meisten Fällen muss das erste Argument von Klassen- und Instanzmethoden nicht annotiert werden und wird für Instanzmethoden als Typ der enthaltenden Klasse und für Klassenmethoden als Typobjekttyp, der dem enthaltenden Klassenobjekt entspricht, angenommen. Darüber hinaus kann das erste Argument einer Instanzmethode mit einer Typvariable annotiert werden. In diesem Fall kann der Rückgabetyp dieselbe Typvariable verwenden, wodurch diese Methode zu einer generischen Funktion wird. Zum Beispiel
T = TypeVar('T', bound='Copyable')
class Copyable:
def copy(self: T) -> T:
# return a copy of self
class C(Copyable): ...
c = C()
c2 = c.copy() # type here should be C
Das Gleiche gilt für Klassenmethoden, die Type[] in einer Annotation des ersten Arguments verwenden
T = TypeVar('T', bound='C')
class C:
@classmethod
def factory(cls: Type[T]) -> T:
# make a new instance of cls
class D(C): ...
d = D.factory() # type here should be D
Beachten Sie, dass einige Typ-Prüfer Einschränkungen für diese Verwendung anwenden können, z. B. die Anforderung einer geeigneten oberen Schranke für die verwendete Typvariable (siehe Beispiele).
Versions- und Plattformprüfung
Typ-Prüfer sollen einfache Versions- und Plattformprüfungen verstehen, z. B.
import sys
if sys.version_info[0] >= 3:
# Python 3 specific definitions
else:
# Python 2 specific definitions
if sys.platform == 'win32':
# Windows specific definitions
else:
# Posix specific definitions
Erwarten Sie nicht, dass ein Prüfer Verschleierungen wie "".join(reversed(sys.platform)) == "xunil" versteht.
Laufzeit- oder Typenprüfung?
Manchmal gibt es Code, der von einem Typ-Prüfer (oder anderen statischen Analysewerkzeugen) gesehen werden muss, aber nicht ausgeführt werden soll. Für solche Situationen definiert das Modul typing eine Konstante, TYPE_CHECKING, die während der Typ-Prüfung (oder anderer statischer Analyse) als True und zur Laufzeit als False betrachtet wird. Beispiel
import typing
if typing.TYPE_CHECKING:
import expensive_mod
def a_func(arg: 'expensive_mod.SomeClass') -> None:
a_var = arg # type: expensive_mod.SomeClass
...
(Beachten Sie, dass die Typannotation in Anführungszeichen stehen muss, um sie zu einer „Vorwärtsreferenz“ zu machen, um die Referenz auf expensive_mod vor der Interpreterlaufzeit zu verbergen. Im Kommentar # type sind keine Anführungszeichen erforderlich.)
Dieser Ansatz kann auch nützlich sein, um Importzyklen zu behandeln.
Beliebige Argumentlisten und Standardargumentwerte
Beliebige Argumentlisten können ebenfalls typ-annotiert werden, so dass die Definition
def foo(*args: str, **kwds: int): ...
akzeptabel ist und bedeutet, dass z. B. alle folgenden Funktionen Aufrufe mit gültigen Argumenttypen darstellen
foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)
Im Körper der Funktion foo wird der Typ der Variablen args als Tuple[str, ...] und der Typ der Variablen kwds als Dict[str, int] abgeleitet.
In Stubs kann es nützlich sein, ein Argument als Standardwert deklarieren zu lassen, ohne den tatsächlichen Standardwert anzugeben. Zum Beispiel
def foo(x: AnyStr, y: AnyStr = ...) -> AnyStr: ...
Wie sollte der Standardwert aussehen? Jede der Optionen "", b"" oder None erfüllt die Typbeschränkung nicht.
In solchen Fällen kann der Standardwert als literales Ellipse angegeben werden, d. h. das obige Beispiel ist wörtlich das, was Sie schreiben würden.
Nur positionsbezogene Argumente
Einige Funktionen sind so konzipiert, dass sie ihre Argumente nur positionell entgegennehmen und erwarten, dass ihre Aufrufer niemals den Namen des Arguments verwenden, um dieses Argument als Schlüsselwort zu übergeben. Alle Argumente mit Namen, die mit __ beginnen, werden als nur positionell angesehen, es sei denn, ihre Namen enden auch mit __
def quux(__x: int, __y__: int = 0) -> None: ...
quux(3, __y__=1) # This call is fine.
quux(__x=3) # This call is an error.
Annotation von Generatorfunktionen und Koroutinen
Der Rückgabetyp von Generatorfunktionen kann durch den generischen Typ Generator[yield_type, send_type, return_type] annotiert werden, der vom Modul typing.py bereitgestellt wird
def echo_round() -> Generator[int, float, str]:
res = yield
while res:
res = yield round(res)
return 'OK'
Koroutinen, die in PEP 492 eingeführt wurden, werden mit der gleichen Syntax wie normale Funktionen annotiert. Die Rückgabe-Typannotation bezieht sich jedoch auf den Typ des await-Ausdrucks, nicht auf den Koroutinen-Typ
async def spam(ignored: int) -> str:
return 'spam'
async def foo() -> None:
bar = await spam(42) # type: str
Das Modul typing.py stellt eine generische Version des ABC collections.abc.Coroutine bereit, um Awaitables zu spezifizieren, die auch send() und throw() Methoden unterstützen. Die Varianz und Reihenfolge der Typvariablen entsprechen denen von Generator, nämlich Coroutine[T_co, T_contra, V_co], zum Beispiel
from typing import List, Coroutine
c = None # type: Coroutine[List[str], str, int]
...
x = c.send('hi') # type: List[str]
async def bar() -> None:
x = await c # type: int
Das Modul stellt auch generische ABCs Awaitable, AsyncIterable und AsyncIterator für Situationen bereit, in denen präzisere Typen nicht spezifiziert werden können
def op() -> typing.Awaitable[str]:
if cond:
return spam(42)
else:
return asyncio.Future(...)
Kompatibilität mit anderen Verwendungen von Funktionsannotationen
Eine Reihe bestehender oder potenzieller Anwendungsfälle für Funktionsannotationen existieren, die mit Typ-Hints inkompatibel sind. Diese können einen statischen Typ-Prüfer verwirren. Da Typ-Hint-Annotationen jedoch kein Laufzeitverhalten haben (außer der Auswertung des Annotationsausdrucks und der Speicherung von Annotationen im Attribut __annotations__ des Funktionsobjekts), macht dies das Programm nicht falsch – es kann nur dazu führen, dass ein Typ-Prüfer fälschlicherweise Warnungen oder Fehler ausgibt.
Um Programmteile zu markieren, die nicht von Typ-Hints abgedeckt werden sollen, können Sie eine oder mehrere der folgenden Optionen verwenden
- einen Kommentar
# type: ignore; - einen
@no_type_checkDekorator auf einer Klasse oder Funktion; - einen benutzerdefinierten Klassen- oder Funktionsdekorator, der mit
@no_type_check_decoratormarkiert ist.
Weitere Details finden Sie in späteren Abschnitten.
Um eine maximale Kompatibilität mit der Offline-Typ-Prüfung zu gewährleisten, könnte es schließlich eine gute Idee sein, Schnittstellen, die auf Annotationen angewiesen sind, auf einen anderen Mechanismus umzustellen, z. B. einen Dekorator. In Python 3.5 gibt es jedoch keinen Druck dazu. Siehe auch die ausführlichere Diskussion unter Abgelehnte Alternativen unten.
Typ-Kommentare
Es wird keine erstklassige Syntaxunterstützung zum expliziten Markieren von Variablen als einem bestimmten Typ hinzugefügt. Um bei komplexen Fällen bei der Typableitung zu helfen, kann ein Kommentar des folgenden Formats verwendet werden
x = [] # type: List[Employee]
x, y, z = [], [], [] # type: List[int], List[int], List[str]
x, y, z = [], [], [] # type: (List[int], List[int], List[str])
a, b, *c = range(5) # type: float, float, List[float]
x = [1, 2] # type: List[int]
Typ-Kommentare sollten auf der letzten Zeile der Anweisung platziert werden, die die Variablendefinition enthält. Sie können auch auf with-Anweisungen und for-Anweisungen direkt nach dem Doppelpunkt platziert werden.
Beispiele für Typ-Kommentare in with- und for-Anweisungen
with frobnicate() as foo: # type: int
# Here foo is an int
...
for x, y in points: # type: float, float
# Here x and y are floats
...
In Stubs kann es nützlich sein, die Existenz einer Variablen zu deklarieren, ohne ihr einen Anfangswert zu geben. Dies kann mit der PEP 526-Syntax für Variablentypannotationen erfolgen
from typing import IO
stream: IO[str]
Die obige Syntax ist für alle Python-Versionen in Stubs akzeptabel. In Nicht-Stub-Code für Python-Versionen 3.5 und früher gibt es jedoch einen Sonderfall
from typing import IO
stream = None # type: IO[str]
Typ-Prüfer sollten sich darüber nicht beschweren (obwohl der Wert None nicht dem gegebenen Typ entspricht) und sie sollten den abgeleiteten Typ auch nicht zu Optional[...] ändern (trotz der Regel, die dies für annotierte Argumente mit einem Standardwert von None tut). Die Annahme hier ist, dass anderer Code sicherstellt, dass die Variable einen Wert vom richtigen Typ erhält, und alle Verwendungen davon ausgehen können, dass die Variable den gegebenen Typ hat.
Der Kommentar # type: ignore sollte auf der Zeile platziert werden, auf die sich der Fehler bezieht
import http.client
errors = {
'not_found': http.client.NOT_FOUND # type: ignore
}
Ein Kommentar # type: ignore auf einer eigenen Zeile am Anfang einer Datei, vor jeglichen Docstrings, Importen oder anderem ausführbaren Code, unterdrückt alle Fehler in der Datei. Leerzeilen und andere Kommentare, wie Shebang-Zeilen und Coding-Cookies, dürfen dem Kommentar # type: ignore vorausgehen.
In einigen Fällen sind möglicherweise Linting-Tools oder andere Kommentare in derselben Zeile wie ein Typ-Kommentar erforderlich. In diesen Fällen sollte der Typ-Kommentar vor anderen Kommentaren und Linter-Markierungen stehen
# type: ignore # <Kommentar oder andere Markierung>
Wenn sich Typ-Hints im Allgemeinen als nützlich erweisen, kann in einer zukünftigen Python-Version eine Syntax für die Typisierung von Variablen bereitgestellt werden. (UPDATE: Diese Syntax wurde in Python 3.6 über PEP 526 hinzugefügt.)
Casts
Gelegentlich benötigt der Typ-Prüfer eine andere Art von Hinweis: Der Programmierer weiß möglicherweise, dass ein Ausdruck einen eingeschränkteren Typ hat, als ein Typ-Prüfer ableiten kann. Zum Beispiel
from typing import List, cast
def find_first_str(a: List[object]) -> str:
index = next(i for i, x in enumerate(a) if isinstance(x, str))
# We only get here if there's at least one string in a
return cast(str, a[index])
Einige Typ-Prüfer können möglicherweise nicht ableiten, dass der Typ von a[index] str ist, und leiten nur object oder Any ab, aber wir wissen, dass es (wenn der Code diesen Punkt erreicht) ein String sein muss. Der Aufruf cast(t, x) sagt dem Typ-Prüfer, dass wir zuversichtlich sind, dass der Typ von x t ist. Zur Laufzeit gibt ein Cast immer den Ausdruck unverändert zurück – er prüft nicht den Typ und konvertiert oder koerziert den Wert nicht.
Casts unterscheiden sich von Typ-Kommentaren (siehe vorheriger Abschnitt). Bei Verwendung eines Typ-Kommentars sollte der Typ-Prüfer immer noch überprüfen, ob der abgeleitete Typ mit dem angegebenen Typ übereinstimmt. Bei Verwendung eines Casts sollte der Typ-Prüfer dem Programmierer blind glauben. Außerdem können Casts in Ausdrücken verwendet werden, während Typ-Kommentare nur für Zuweisungen gelten.
NewType-Hilfsfunktion
Es gibt auch Situationen, in denen ein Programmierer logische Fehler vermeiden möchte, indem er einfache Klassen erstellt. Zum Beispiel
class UserId(int):
pass
def get_by_user_id(user_id: UserId):
...
Dieser Ansatz führt jedoch zu einem Laufzeit-Overhead. Um dies zu vermeiden, bietet typing.py eine Hilfsfunktion NewType, die einfache eindeutige Typen mit fast keinem Laufzeit-Overhead erstellt. Für einen statischen Typ-Prüfer ist Derived = NewType('Derived', Base) ungefähr äquivalent zu einer Definition
class Derived(Base):
def __init__(self, _x: Base) -> None:
...
Während zur Laufzeit gibt NewType('Derived', Base) eine Dummy-Funktion zurück, die einfach ihr Argument zurückgibt. Typ-Prüfer erfordern explizite Casts von int, wo UserId erwartet wird, während sie implizit von UserId casten, wo int erwartet wird. Beispiele
UserId = NewType('UserId', int)
def name_by_id(user_id: UserId) -> str:
...
UserId('user') # Fails type check
name_by_id(42) # Fails type check
name_by_id(UserId(42)) # OK
num = UserId(5) + 1 # type: int
NewType akzeptiert genau zwei Argumente: einen Namen für den neuen eindeutigen Typ und eine Basisklasse. Letztere sollte eine richtige Klasse sein (d. h. kein Typkonstrukt wie Union usw.) oder ein anderer eindeutiger Typ, der durch Aufruf von NewType erstellt wurde. Die von NewType zurückgegebene Funktion akzeptiert nur ein Argument; dies ist äquivalent zur Unterstützung nur eines Konstruktors, der eine Instanz der Basisklasse akzeptiert (siehe oben). Beispiel
class PacketId:
def __init__(self, major: int, minor: int) -> None:
self._major = major
self._minor = minor
TcpPacketId = NewType('TcpPacketId', PacketId)
packet = PacketId(100, 100)
tcp_packet = TcpPacketId(packet) # OK
tcp_packet = TcpPacketId(127, 0) # Fails in type checker and at runtime
Sowohl isinstance als auch issubclass sowie Unterklassifizierung schlagen für NewType('Derived', Base) fehl, da Funktionsobjekte diese Operationen nicht unterstützen.
Stub-Dateien
Stub-Dateien sind Dateien, die Typ-Hints enthalten, die nur für den Typ-Prüfer, nicht zur Laufzeit verwendet werden. Es gibt mehrere Anwendungsfälle für Stub-Dateien
- Erweiterungsmodule
- Drittanbieter-Module, deren Autoren noch keine Typ-Hints hinzugefügt haben
- Standardbibliotheksmodule, für die noch keine Typ-Hints geschrieben wurden
- Module, die mit Python 2 und 3 kompatibel sein müssen
- Module, die Annotationen für andere Zwecke verwenden
Stub-Dateien haben die gleiche Syntax wie reguläre Python-Module. Es gibt eine Funktion des Moduls typing, die in Stub-Dateien anders ist: der Dekorator @overload, der unten beschrieben wird.
Der Typ-Prüfer sollte nur Funktionssignaturen in Stub-Dateien überprüfen; Es wird empfohlen, dass Funktionskörper in Stub-Dateien nur ein einzelnes Ellipse (...) sind.
Der Typ-Prüfer sollte einen konfigurierbaren Suchpfad für Stub-Dateien haben. Wenn eine Stub-Datei gefunden wird, sollte der Typ-Prüfer nicht das entsprechende „echte“ Modul lesen.
Während Stub-Dateien syntaktisch gültige Python-Module sind, verwenden sie die Erweiterung .pyi, um die Wartung von Stub-Dateien im selben Verzeichnis wie das entsprechende echte Modul zu ermöglichen. Dies verstärkt auch die Vorstellung, dass von Stub-Dateien kein Laufzeitverhalten erwartet werden sollte.
Zusätzliche Hinweise zu Stub-Dateien
- Module und Variablen, die in den Stub importiert werden, werden nicht als vom Stub exportiert betrachtet, es sei denn, der Import verwendet die Form
import ... as ...oder die äquivalente Formfrom ... import ... as .... (UPDATE: Zur Klarstellung ist die Absicht hier, dass nur Namen, die mit der FormX as Ximportiert werden, exportiert werden, d. h. der Name vor und nachasmuss gleich sein.) - Als Ausnahme zu dem vorherigen Aufzählungspunkt werden jedoch alle Objekte, die mit
from ... import *in einen Stub importiert werden, als exportiert betrachtet. (Dies erleichtert den erneuten Export aller Objekte aus einem bestimmten Modul, die je nach Python-Version variieren können.) - Genau wie in normalen Python-Dateien werden Untermodule beim Importieren automatisch zu exportierten Attributen ihres übergeordneten Moduls. Wenn das Paket
spambeispielsweise die folgende Verzeichnisstruktur hatspam/ __init__.pyi ham.pyi
wobei
__init__.pyieine Zeile wiefrom . import hamoderfrom .ham import Hamenthält, dann isthamein exportiertes Attribut vonspam. - Stub-Dateien können unvollständig sein. Um Typüberprüfer darauf aufmerksam zu machen, kann die Datei den folgenden Code enthalten
def __getattr__(name) -> Any: ...
Jeder Bezeichner, der nicht im Stub definiert ist, wird daher als vom Typ
Anyangenommen.
Funktions-/Methodenüberladung
Der Dekorator @overload ermöglicht die Beschreibung von Funktionen und Methoden, die mehrere verschiedene Kombinationen von Argumenttypen unterstützen. Dieses Muster wird häufig in eingebauten Modulen und Typen verwendet. Zum Beispiel kann die Methode __getitem__() des Typs bytes wie folgt beschrieben werden
from typing import overload
class bytes:
...
@overload
def __getitem__(self, i: int) -> int: ...
@overload
def __getitem__(self, s: slice) -> bytes: ...
Diese Beschreibung ist präziser als das, was mit Vereinigungen (Unions) möglich wäre (die die Beziehung zwischen den Argument- und Rückgabetypen nicht ausdrücken können)
from typing import Union
class bytes:
...
def __getitem__(self, a: Union[int, slice]) -> Union[int, bytes]: ...
Ein weiteres Beispiel, bei dem @overload nützlich ist, ist der Typ der eingebauten Funktion map(), die je nach Typ des Aufrufbaren eine unterschiedliche Anzahl von Argumenten annimmt
from typing import Callable, Iterable, Iterator, Tuple, TypeVar, overload
T1 = TypeVar('T1')
T2 = TypeVar('T2')
S = TypeVar('S')
@overload
def map(func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]: ...
@overload
def map(func: Callable[[T1, T2], S],
iter1: Iterable[T1], iter2: Iterable[T2]) -> Iterator[S]: ...
# ... and we could add more items to support more than two iterables
Beachten Sie, dass wir auch problemlos Elemente hinzufügen könnten, um map(None, ...) zu unterstützen
@overload
def map(func: None, iter1: Iterable[T1]) -> Iterable[T1]: ...
@overload
def map(func: None,
iter1: Iterable[T1],
iter2: Iterable[T2]) -> Iterable[Tuple[T1, T2]]: ...
Verwendungen des Dekorators @overload, wie oben gezeigt, sind für Stub-Dateien geeignet. In regulären Modulen muss auf eine Reihe von @overload-dekorierten Definitionen genau eine nicht @overload-dekorierte Definition (für dieselbe Funktion/Methode) folgen. Die @overload-dekorierten Definitionen sind nur für den Typüberprüfer bestimmt, da sie von der nicht @overload-dekorierten Definition überschrieben werden, während letztere zur Laufzeit verwendet, aber von einem Typüberprüfer ignoriert werden sollte. Zur Laufzeit löst der direkte Aufruf einer @overload-dekorierten Funktion NotImplementedError aus. Hier ist ein Beispiel für eine Nicht-Stub-Überladung, die nicht einfach mit einer Vereinigung (Union) oder einer Typvariable ausgedrückt werden kann
@overload
def utf8(value: None) -> None:
pass
@overload
def utf8(value: bytes) -> bytes:
pass
@overload
def utf8(value: unicode) -> bytes:
pass
def utf8(value):
<actual implementation>
HINWEIS: Obwohl es möglich wäre, eine Mehrfachdispatch-Implementierung mit dieser Syntax bereitzustellen, würde ihre Implementierung die Verwendung von sys._getframe() erfordern, was verpönt ist. Außerdem ist das Entwerfen und Implementieren eines effizienten Mehrfachdispatch-Mechanismus schwierig, weshalb frühere Versuche zugunsten von functools.singledispatch() aufgegeben wurden. (Siehe PEP 443, insbesondere den Abschnitt „Alternative Ansätze“.) In Zukunft werden wir möglicherweise ein zufriedenstellendes Mehrfachdispatch-Design entwickeln, aber wir möchten nicht, dass ein solches Design durch die für Typ-Hints in Stub-Dateien definierte Überladungs-Syntax eingeschränkt wird. Es ist auch möglich, dass sich beide Features unabhängig voneinander entwickeln (da Überladungen in Typüberprüfern andere Anwendungsfälle und Anforderungen haben als Mehrfachdispatch zur Laufzeit – z. B. wird letzteres wahrscheinlich keine generischen Typen unterstützen).
Eine eingeschränkte TypeVar kann oft anstelle der Verwendung des Dekorators @overload verwendet werden. Zum Beispiel sind die Definitionen von concat1 und concat2 in dieser Stub-Datei äquivalent
from typing import TypeVar, Text
AnyStr = TypeVar('AnyStr', Text, bytes)
def concat1(x: AnyStr, y: AnyStr) -> AnyStr: ...
@overload
def concat2(x: str, y: str) -> str: ...
@overload
def concat2(x: bytes, y: bytes) -> bytes: ...
Einige Funktionen, wie map oder bytes.__getitem__ oben, können nicht präzise mit Typvariablen dargestellt werden. Im Gegensatz zu @overload können Typvariablen jedoch auch außerhalb von Stub-Dateien verwendet werden. Wir empfehlen, @overload nur in Fällen zu verwenden, in denen eine Typvariable nicht ausreicht, aufgrund ihres speziellen Stub-only-Status.
Ein weiterer wichtiger Unterschied zwischen Typvariablen wie AnyStr und der Verwendung von @overload ist, dass erstere auch zur Definition von Einschränkungen für generische Klassen-Typ-Parameter verwendet werden können. Zum Beispiel ist der Typ-Parameter der generischen Klasse typing.IO eingeschränkt (nur IO[str], IO[bytes] und IO[Any] sind gültig)
class IO(Generic[AnyStr]): ...
Speichern und Verteilen von Stub-Dateien
Die einfachste Form der Speicherung und Verteilung von Stub-Dateien ist, sie zusammen mit Python-Modulen im selben Verzeichnis abzulegen. Dies erleichtert das Auffinden sowohl für Programmierer als auch für Werkzeuge. Da Paketbetreuer jedoch frei sind, Typ-Hinweise nicht zu ihren Paketen hinzuzufügen, werden auch von Drittanbietern installierbare Stubs von PyPI unterstützt, die mit pip installiert werden können. In diesem Fall müssen wir drei Probleme berücksichtigen: Benennung, Versionierung und Installationspfad.
Dieses PEP gibt keine Empfehlung für ein Benennungsschema für Stub-Pakete von Drittanbietern. Die Auffindbarkeit wird hoffentlich auf der Beliebtheit des Pakets beruhen, wie z. B. bei Django-Paketen.
Stub-Dateien von Drittanbietern müssen mit der niedrigsten Version des Quellpakets, mit dem sie kompatibel sind, versioniert werden. Beispiel: FooPackage hat die Versionen 1.0, 1.1, 1.2, 1.3, 2.0, 2.1, 2.2. Es gibt API-Änderungen in den Versionen 1.1, 2.0 und 2.2. Der Betreuer des Stub-Pakets ist frei, Stubs für alle Versionen zu veröffentlichen, aber mindestens 1.0, 1.1, 2.0 und 2.2 sind erforderlich, damit der Endbenutzer alle Versionen typüberprüfen kann. Dies liegt daran, dass der Benutzer weiß, dass die nächstgelegene geringere oder gleiche Version von Stubs kompatibel ist. Im obigen Beispiel würde der Benutzer für FooPackage 1.3 die Stub-Version 1.1 wählen.
Beachten Sie, dass die Verwendung der „neuesten“ Stub-Dateien bei Verwendung des „neuesten“ Quellpakets im Allgemeinen auch funktionieren sollte, wenn sie häufig aktualisiert werden.
Stub-Pakete von Drittanbietern können jeden Speicherort für die Stub-Speicherung verwenden. Typüberprüfer sollten mit PYTHONPATH danach suchen. Ein standardmäßiges Fallback-Verzeichnis, das immer überprüft wird, ist shared/typehints/pythonX.Y/ (für ein beliebiges PythonX.Y, wie vom Typüberprüfer ermittelt, nicht nur die installierte Version). Da pro Umgebung nur ein Paket für eine bestimmte Python-Version installiert werden kann, wird unter diesem Verzeichnis keine zusätzliche Versionierung durchgeführt (genau wie bei Bare-Directory-Installationen von pip in site-packages). Autoren von Stub-Paketen könnten den folgenden Ausschnitt in setup.py verwenden
...
data_files=[
(
'shared/typehints/python{}.{}'.format(*sys.version_info[:2]),
pathlib.Path(SRC_PATH).glob('**/*.pyi'),
),
],
...
(UPDATE: Seit Juni 2018 hat sich die empfohlene Methode zur Verteilung von Typ-Hinweisen für Pakete von Drittanbietern geändert – zusätzlich zu typeshed (siehe nächster Abschnitt) gibt es nun einen Standard für die Verteilung von Typ-Hinweisen, PEP 561. Er unterstützt separat installierbare Pakete, die Stubs enthalten, Stub-Dateien, die in derselben Distribution wie der ausführbare Code eines Pakets enthalten sind, und Inline-Typ-Hinweise; die beiden letzteren Optionen werden durch die Aufnahme einer Datei namens py.typed in das Paket aktiviert.)
Das Typeshed-Repository
Es gibt ein gemeinsames Repository, in dem nützliche Stubs gesammelt werden. Richtlinien bezüglich der hier gesammelten Stubs werden separat festgelegt und in der Dokumentation des Repos beibehalten. Beachten Sie, dass Stubs für ein bestimmtes Paket hier nicht enthalten sein werden, wenn die Paketbesitzer ausdrücklich deren Auslassung verlangt haben.
Ausnahmen
Es wird keine Syntax zum Auflisten explizit ausgelöster Ausnahmen vorgeschlagen. Derzeit ist der einzige bekannte Anwendungsfall für diese Funktion dokumentarisch, in diesem Fall wird empfohlen, diese Informationen in einem Docstring zu platzieren.
Das Modul typing
Um die Nutzung statischer Typüberprüfung auch für Python 3.5 und ältere Versionen zu ermöglichen, ist ein einheitlicher Namensraum erforderlich. Zu diesem Zweck wird ein neues Modul in der Standardbibliothek eingeführt, das typing heißt.
Es definiert die grundlegenden Bausteine für den Aufbau von Typen (z. B. Any), Typen, die generische Varianten von integrierten Sammlungen darstellen (z. B. List), Typen, die generische Sammlungs-ABCs darstellen (z. B. Sequence) und eine kleine Sammlung von Hilfsdefinitionen.
Beachten Sie, dass spezielle Typkonstrukte wie Any, Union und mit TypeVar definierte Typvariablen nur im Kontext von Typannotationen unterstützt werden und Generic nur als Basisklasse verwendet werden darf. Alle diese (außer unparametrisierten Generics) lösen TypeError aus, wenn sie in isinstance oder issubclass vorkommen.
Grundlegende Bausteine
- Any, verwendet als
def get(key: str) -> Any: ... - Union, verwendet als
Union[Type1, Type2, Type3] - Callable, verwendet als
Callable[[Arg1Type, Arg2Type], ReturnType] - Tuple, verwendet durch Auflistung der Elementtypen, z. B.
Tuple[int, int, str]. Das leere Tupel kann alsTuple[()]typisiert werden. Homogene Tupel beliebiger Länge können mit einem Typ und einer Ellipse ausgedrückt werden, z. B.Tuple[int, ...]. (Die...hier sind Teil der Syntax, eine literale Ellipse.) - TypeVar, verwendet als
X = TypeVar('X', Type1, Type2, Type3)oder einfachY = TypeVar('Y')(siehe oben für weitere Details) - Generic, verwendet zur Erstellung benutzerdefinierter generischer Klassen
- Type, verwendet zur Annotation von Klassenobjekten
Generische Varianten von integrierten Sammlungen
- Dict, verwendet als
Dict[key_type, value_type] - DefaultDict, verwendet als
DefaultDict[key_type, value_type], eine generische Variante voncollections.defaultdict - List, verwendet als
List[element_type] - Set, verwendet als
Set[element_type]. Siehe Anmerkung zuAbstractSetunten. - FrozenSet, verwendet als
FrozenSet[element_type]
Hinweis: Dict, DefaultDict, List, Set und FrozenSet sind hauptsächlich für die Annotation von Rückgabewerten nützlich. Für Argumente werden die unten definierten abstrakten Sammlungs-Typen bevorzugt, z. B. Mapping, Sequence oder AbstractSet.
Generische Varianten von Container-ABCs (und einige Nicht-Container)
- Awaitable
- AsyncIterable
- AsyncIterator
- ByteString
- Callable (siehe oben, hier zur Vollständigkeit aufgeführt)
- Collection
- Container
- ContextManager
- Coroutine
- Generator, verwendet als
Generator[yield_type, send_type, return_type]. Dies stellt den Rückgabewert von Generatorfunktionen dar. Er ist eine Unterklasse vonIterableund verfügt über zusätzliche Typvariablen für den Typ, der von der Methodesend()akzeptiert wird (er ist kontravariant in dieser Variablen – ein Generator, der das Senden einerEmployee-Instanz akzeptiert, ist in einem Kontext gültig, in dem ein Generator benötigt wird, der das Senden vonManager-Instanzen akzeptiert) und den Rückgabetyp des Generators. - Hashable (nicht generisch, aber zur Vollständigkeit vorhanden)
- ItemsView
- Iterable
- Iterator
- KeysView
- Mapping
- MappingView
- MutableMapping
- MutableSequence
- MutableSet
- Sequence
- Set, umbenannt in
AbstractSet. Diese Namensänderung war erforderlich, daSetim Modultypingset()mit Generics bedeutet. - Sized (nicht generisch, aber zur Vollständigkeit vorhanden)
- ValuesView
Einige einmalige Typen werden definiert, die auf einzelne spezielle Methoden prüfen (ähnlich wie Hashable oder Sized)
- Reversible, zum Testen von
__reversed__ - SupportsAbs, zum Testen von
__abs__ - SupportsComplex, zum Testen von
__complex__ - SupportsFloat, zum Testen von
__float__ - SupportsInt, zum Testen von
__int__ - SupportsRound, zum Testen von
__round__ - SupportsBytes, zum Testen von
__bytes__
Hilfsdefinitionen
- Optional, definiert durch
Optional[t] == Union[t, None] - Text, ein einfacher Alias für
strin Python 3, fürunicodein Python 2 - AnyStr, definiert als
TypeVar('AnyStr', Text, bytes) - NamedTuple, verwendet als
NamedTuple(type_name, [(field_name, field_type), ...])und äquivalent zucollections.namedtuple(type_name, [field_name, ...]). Dies ist nützlich, um die Typen der Felder eines Named-Tuple-Typs zu deklarieren. - NewType, verwendet zur Erstellung eindeutiger Typen mit geringem Laufzeit-Overhead
UserId = NewType('UserId', int) - cast(), zuvor beschrieben
- @no_type_check, ein Dekorator zum Deaktivieren der Typüberprüfung pro Klasse oder Funktion (siehe unten)
- @no_type_check_decorator, ein Dekorator zur Erstellung eigener Dekoratoren mit derselben Bedeutung wie
@no_type_check(siehe unten) - @type_check_only, ein Dekorator, der nur während der Typüberprüfung für die Verwendung in Stub-Dateien verfügbar ist (siehe oben); markiert eine Klasse oder Funktion als zur Laufzeit nicht verfügbar
- @overload, zuvor beschrieben
- get_type_hints(), eine Hilfsfunktion zum Abrufen der Typ-Hinweise einer Funktion oder Methode. Bei einem Funktions- oder Methodenobjekt gibt sie ein Dictionary im selben Format wie
__annotations__zurück, evaluiert aber Rückwärtsreferenzen (die als Zeichenkettenliterale gegeben sind) als Ausdrücke im Kontext der ursprünglichen Funktions- oder Methodendefinition. - TYPE_CHECKING,
Falsezur Laufzeit, aberTruefür Typüberprüfer
I/O-bezogene Typen
- IO (generisch über
AnyStr) - BinaryIO (eine einfache Unterklasse von
IO[bytes]) - TextIO (eine einfache Unterklasse von
IO[str])
Typen im Zusammenhang mit regulären Ausdrücken und dem Modul re
- Match und Pattern, Typen der Ergebnisse von
re.match()undre.compile()(generisch überAnyStr)
Vorgeschlagene Syntax für Python 2.7 und überlappenden Code
Einige Werkzeuge möchten möglicherweise Typannotationen in Code unterstützen, der mit Python 2.7 kompatibel sein muss. Zu diesem Zweck enthält dieses PEP eine vorgeschlagene (aber nicht zwingende) Erweiterung, bei der Funktionsannotationen in einem Kommentar # type: platziert werden. Ein solcher Kommentar muss unmittelbar nach dem Funktionsheader (vor dem Docstring) platziert werden. Beispiel: Der folgende Python 3-Code
def embezzle(self, account: str, funds: int = 1000000, *fake_receipts: str) -> None:
"""Embezzle funds from account using fake receipts."""
<code goes here>
ist äquivalent zu folgendem
def embezzle(self, account, funds=1000000, *fake_receipts):
# type: (str, int, *str) -> None
"""Embezzle funds from account using fake receipts."""
<code goes here>
Beachten Sie, dass bei Methoden kein Typ für self benötigt wird.
Für eine argumentlose Methode würde es so aussehen
def load_cache(self):
# type: () -> bool
<code>
Manchmal möchten Sie den Rückgabetyp für eine Funktion oder Methode angeben, ohne (noch) die Argumenttypen anzugeben. Um dies explizit zu unterstützen, kann die Argumentliste durch eine Ellipse ersetzt werden. Beispiel
def send_email(address, sender, cc, bcc, subject, body):
# type: (...) -> bool
"""Send an email message. Return True if successful."""
<code>
Manchmal haben Sie eine lange Liste von Parametern und die Angabe ihrer Typen in einem einzigen Kommentar # type: wäre umständlich. Zu diesem Zweck können Sie die Argumente einzeln auflisten und nach dem zugehörigen Komma, falls vorhanden, einen Kommentar # type: pro Zeile hinzufügen. Um den Rückgabetyp anzugeben, verwenden Sie die Ellipsen-Syntax. Die Angabe des Rückgabetyps ist nicht zwingend erforderlich und nicht jedes Argument muss einen Typ erhalten. Eine Zeile mit einem Kommentar # type: sollte genau ein Argument enthalten. Der Typkommentar für das letzte Argument (falls vorhanden) sollte der schließenden Klammer vorausgehen. Beispiel
def send_email(address, # type: Union[str, List[str]]
sender, # type: str
cc, # type: Optional[List[str]]
bcc, # type: Optional[List[str]]
subject='',
body=None # type: List[str]
):
# type: (...) -> bool
"""Send an email message. Return True if successful."""
<code>
Anmerkungen
- Werkzeuge, die diese Syntax unterstützen, sollten sie unabhängig von der zu überprüfenden Python-Version unterstützen. Dies ist notwendig, um Code zu unterstützen, der sowohl Python 2 als auch Python 3 umfasst.
- Es ist einem Argument oder Rückgabewert nicht gestattet, sowohl eine Typannotation als auch einen Typkommentar zu haben.
- Bei Verwendung der Kurzform (z. B.
# type: (str, int) -> None) müssen alle Argumente berücksichtigt werden, mit Ausnahme des ersten Arguments von Instanz- und Klassenmethoden (diese werden normalerweise weggelassen, es ist aber erlaubt, sie einzuschließen). - Der Rückgabetyp ist für die Kurzform zwingend erforderlich. Wenn in Python 3 einige Argumente oder der Rückgabetyp weggelassen würden, sollte die Python 2-Notation
Anyverwenden. - Bei Verwendung der Kurzform setzen Sie für
*argsund**kwdseinen oder zwei Sterne vor die entsprechende Typannotation. (Wie bei Python 3-Annotationen bezeichnet die Annotation hier den Typ der einzelnen Argumentwerte, nicht des Tupels/Dictionaries, das Sie als speziellen Argumentwertargsoderkwdserhalten.) - Wie andere Typkommentare müssen auch Namen, die in den Annotationen verwendet werden, von dem Modul, das die Annotation enthält, importiert oder definiert sein.
- Bei Verwendung der Kurzform muss die gesamte Annotation einzeilig sein.
- Die Kurzform kann auch in derselben Zeile wie die schließende Klammer erscheinen, z. B.
def add(a, b): # type: (int, int) -> int return a + b
- Falsch platzierte Typkommentare werden von einem Typüberprüfer als Fehler gekennzeichnet. Bei Bedarf könnten solche Kommentare doppelt auskommentiert werden. Zum Beispiel
def f(): '''Docstring''' # type: () -> None # Error! def g(): '''Docstring''' # # type: () -> None # This is OK
Bei der Überprüfung von Python 2.7-Code sollten Typüberprüfer die Typen int und long als äquivalent behandeln. Für Parameter mit dem Typ Text sollten Argumente vom Typ str sowie unicode akzeptabel sein.
Abgelehnte Alternativen
Während der Diskussion früherer Entwürfe dieses PEP wurden verschiedene Einwände erhoben und Alternativen vorgeschlagen. Wir besprechen einige davon hier und erklären, warum wir sie ablehnen.
Mehrere Haupteinwände wurden erhoben.
Welche Klammern für generische Typparameter?
Die meisten Menschen sind mit der Verwendung von spitzen Klammern (z. B. List<int>) in Sprachen wie C++, Java, C# und Swift zur Angabe der Parametrisierung generischer Typen vertraut. Das Problem dabei ist, dass sie besonders für einen einfachen Parser wie Python sehr schwer zu parsen sind. In den meisten Sprachen werden die Mehrdeutigkeiten normalerweise dadurch behoben, dass spitze Klammern nur in bestimmten syntaktischen Positionen erlaubt sind, wo allgemeine Ausdrücke nicht zulässig sind. (Und auch durch die Verwendung sehr leistungsfähiger Parsing-Techniken, die über einen beliebigen Code-Abschnitt zurückverfolgen können.)
Aber in Python möchten wir, dass Typausdrücke (syntaktisch) mit anderen Ausdrücken identisch sind, sodass wir z. B. Variablendeklarationen zur Erstellung von Typ-Aliassen verwenden können. Betrachten Sie diesen einfachen Typausdruck
List<int>
Aus Sicht des Python-Parsers beginnt der Ausdruck mit denselben vier Token (NAME, LESS, NAME, GREATER) wie ein verketteter Vergleich
a < b > c # I.e., (a < b) and (b > c)
Wir können sogar ein Beispiel erstellen, das auf beide Arten geparst werden könnte
a < b > [ c ]
Unter der Annahme, dass wir spitze Klammern in der Sprache hätten, könnte dies wie folgt interpretiert werden
(a<b>)[c] # I.e., (a<b>).__getitem__(c)
a < b > ([c]) # I.e., (a < b) and (b > [c])
Es wäre sicherlich möglich, eine Regel zu finden, um solche Fälle aufzulösen, aber für die meisten Benutzer würden sich die Regeln willkürlich und komplex anfühlen. Außerdem müssten wir den CPython-Parser (und jeden anderen Python-Parser) drastisch ändern. Es sollte beachtet werden, dass der aktuelle Parser von Python absichtlich „dumm“ ist – eine einfache Grammatik ist für Benutzer leichter nachvollziehbar.
Aus all diesen Gründen sind eckige Klammern (z. B. List[int]) die (seit langem) bevorzugte Syntax für generische Typ-Parameter. Sie können durch die Definition der Methode __getitem__() auf der Metaklasse implementiert werden, und es ist überhaupt keine neue Syntax erforderlich. Diese Option funktioniert in allen neueren Python-Versionen (ab Python 2.2). Python ist mit dieser syntaktischen Wahl nicht allein – generische Klassen in Scala verwenden ebenfalls eckige Klammern.
Was ist mit bestehenden Verwendungen von Annotationen?
Ein Argumentationsstrang weist darauf hin, dass PEP 3107 die Verwendung beliebiger Ausdrücke in Funktionsannotationen ausdrücklich unterstützt. Der neue Vorschlag wird dann als inkompatibel mit der Spezifikation von PEP 3107 betrachtet.
Unsere Antwort darauf ist, dass der aktuelle Vorschlag erstens keine direkten Inkompatibilitäten einführt, sodass Programme, die Annotationen in Python 3.4 verwenden, auch in Python 3.5 korrekt und ohne Benachteiligung funktionieren.
Wir hoffen, dass Typ-Hinweise schließlich der alleinige Verwendungszweck für Annotationen werden, aber dies erfordert weitere Diskussionen und eine Übergangsfrist nach der anfänglichen Einführung des `typing`-Moduls mit Python 3.5. Dieses PEP wird einen provisorischen Status haben (siehe PEP 411), bis Python 3.6 veröffentlicht wird. Das schnellstmöglich denkbare Schema würde die stille Deprekation von Nicht-Typ-Hinweis-Annotationen in 3.6, die vollständige Deprekation in 3.7 und die Erklärung von Typ-Hinweisen als alleinigen erlaubten Verwendungszweck von Annotationen in Python 3.8 einführen. Dies sollte Autoren von Paketen, die Annotationen verwenden, ausreichend Zeit geben, einen anderen Ansatz zu finden, selbst wenn Typ-Hinweise über Nacht erfolgreich werden.
(UPDATE: Seit Herbst 2017 hat sich der Zeitplan für das Ende des provisorischen Status dieses PEP und des Moduls typing.py geändert, und damit auch der Zeitplan für die Deprekation anderer Verwendungen von Annotationen. Für den aktualisierten Zeitplan siehe PEP 563.)
Ein weiteres mögliches Ergebnis wäre, dass Typ-Hinweise schließlich die Standardbedeutung für Annotationen werden, aber dass es immer eine Option geben wird, sie zu deaktivieren. Zu diesem Zweck definiert der aktuelle Vorschlag einen Dekorator @no_type_check, der die Standardinterpretation von Annotationen als Typ-Hinweise in einer bestimmten Klasse oder Funktion deaktiviert. Er definiert auch einen Meta-Dekorator @no_type_check_decorator, der verwendet werden kann, um einen Dekorator (!) zu dekorieren, wodurch Annotationen in jeder Funktion oder Klasse, die mit letzterem dekoriert sind, vom Typüberprüfer ignoriert werden.
Es gibt auch # type: ignore-Kommentare, und statische Prüfer sollten Konfigurationsoptionen unterstützen, um die Typüberprüfung in ausgewählten Paketen zu deaktivieren.
Trotz all dieser Optionen wurden Vorschläge zirkuliert, um Typ-Hinweise und andere Formen von Annotationen für einzelne Argumente nebeneinander zuzulassen. Ein Vorschlag besagt, dass, wenn eine Annotation für ein bestimmtes Argument eine Dictionary-Literal ist, jeder Schlüssel eine andere Form der Annotation darstellt und der Schlüssel 'type' für Typ-Hinweise verwendet würde. Das Problem mit dieser Idee und ihren Varianten ist, dass die Notation sehr „laut“ und schwer zu lesen wird. Außerdem wäre in den meisten Fällen, in denen vorhandene Bibliotheken Annotationen verwenden, die Kombination mit Typ-Hinweisen kaum erforderlich. Der einfachere Ansatz, Typ-Hinweise selektiv zu deaktivieren, erscheint daher ausreichend.
Das Problem von Vorwärtsdeklarationen
Der aktuelle Vorschlag ist zugegebenermaßen sub-optimal, wenn Typ-Hinweise Rückwärtsreferenzen enthalten müssen. Python verlangt, dass alle Namen definiert sind, wenn sie verwendet werden. Abgesehen von zirkulären Importen ist dies selten ein Problem: „Verwendung“ bedeutet hier „zur Laufzeit nachschlagen“, und bei den meisten „rückwärtsgerichteten“ Referenzen gibt es kein Problem, sicherzustellen, dass ein Name definiert ist, bevor die verwendende Funktion aufgerufen wird.
Das Problem bei Typ-Hinweisen ist, dass Annotationen (gemäß PEP 3107 und ähnlich wie Standardwerte) zum Zeitpunkt der Funktionsdefinition ausgewertet werden, und somit müssen alle in einer Annotation verwendeten Namen definiert sein, wenn die Funktion definiert wird. Ein häufiges Szenario ist die Klassendefinition, deren Methoden in ihren Annotationen auf die Klasse selbst verweisen müssen. (Allgemeiner kann dies auch bei wechselseitig rekursiven Klassen auftreten.) Dies ist für Containertypen zum Beispiel natürlich
class Node:
"""Binary tree node."""
def __init__(self, left: Node, right: Node):
self.left = left
self.right = right
So wie es geschrieben ist, wird es wegen der Eigenart von Python, dass Klassennamen definiert werden, sobald der gesamte Körper der Klasse ausgeführt wurde, nicht funktionieren. Unsere Lösung, die nicht besonders elegant, aber zweckmäßig ist, besteht darin, die Verwendung von Zeichenkettenliteralen in Annotationen zu erlauben. Die meiste Zeit müssen Sie dies jedoch nicht verwenden – die meisten *Verwendungen* von Typ-Hinweisen werden voraussichtlich auf eingebaute Typen oder in anderen Modulen definierte Typen verweisen.
Ein Gegenentwurf würde die Semantik von Typ-Hinweisen ändern, sodass sie zur Laufzeit überhaupt nicht ausgewertet werden (schließlich findet die Typüberprüfung offline statt, warum sollten Typ-Hinweise zur Laufzeit ausgewertet werden müssen?). Dies würde natürlich die Abwärtskompatibilität verletzen, da der Python-Interpreter nicht weiß, ob eine bestimmte Annotation als Typ-Hinweis oder etwas anderes gedacht ist.
Ein Kompromiss ist möglich, bei dem ein `__future__`-Import *alle* Annotationen in einem bestimmten Modul in Zeichenkettenliterale umwandeln könnte, wie folgt
from __future__ import annotations
class ImSet:
def add(self, a: ImSet) -> List[ImSet]: ...
assert ImSet.add.__annotations__ == {'a': 'ImSet', 'return': 'List[ImSet]'}
Eine solche __future__ Import-Anweisung kann in einem separaten PEP vorgeschlagen werden.
(UPDATE: Diese __future__ Import-Anweisung und ihre Konsequenzen werden in PEP 563 diskutiert.)
Der Doppelpunkt
Einige kreative Seelen haben versucht, Lösungen für dieses Problem zu erfinden. Zum Beispiel wurde vorgeschlagen, ein doppeltes Doppelpunktzeichen (::) für Typ-Hints zu verwenden, wodurch zwei Probleme auf einmal gelöst werden: die Unterscheidung zwischen Typ-Hints und anderen Annotationen sowie die Änderung der Semantik, um die Laufzeitauswertung auszuschließen. Es gibt jedoch mehrere Probleme mit dieser Idee.
- Es ist hässlich. Der einfache Doppelpunkt in Python hat viele Verwendungszwecke, und alle sehen vertraut aus, da sie der Verwendung des Doppelpunkts in englischen Texten ähneln. Dies ist eine allgemeine Faustregel, an die sich Python für die meisten Formen der Interpunktion hält; Ausnahmen sind typischerweise aus anderen Programmiersprachen bekannt. Aber diese Verwendung von
::ist im Englischen unbekannt und wird in anderen Sprachen (z. B. C++) als Bereichsoperator verwendet, was etwas ganz anderes ist. Im Gegensatz dazu liest sich der einfache Doppelpunkt für Typ-Hints natürlich – und kein Wunder, da er sorgfältig für diesen Zweck entwickelt wurde (die Idee ging lange vor PEP 3107 voraus). Er wird auch in anderen Sprachen von Pascal bis Swift in gleicher Weise verwendet. - Was würden Sie für Rückgabetyp-Annotationen tun?
- Es ist eigentlich eine Funktion, dass Typ-Hints zur Laufzeit ausgewertet werden.
- Typ-Hints zur Laufzeit verfügbar zu machen, ermöglicht den Aufbau von Laufzeit-Typprüfern auf Basis von Typ-Hints.
- Es fängt Fehler auf, auch wenn der Typ-Checker nicht ausgeführt wird. Da es sich um ein separates Programm handelt, können Benutzer wählen, es nicht auszuführen (oder es gar nicht zu installieren), möchten aber dennoch Typ-Hints als prägnante Form der Dokumentation verwenden. Fehlerhafte Typ-Hints sind auch für die Dokumentation nutzlos.
- Da es sich um eine neue Syntax handelt, würde die Verwendung des doppelten Doppelpunkts für Typ-Hints diese auf Code beschränken, der nur mit Python 3.5 funktioniert. Durch die Verwendung vorhandener Syntax kann der aktuelle Vorschlag problemlos für ältere Versionen von Python 3 funktionieren. (Tatsächlich unterstützt mypy Python 3.2 und neuer.)
- Wenn Typ-Hints erfolgreich werden, könnten wir uns in Zukunft entscheiden, neue Syntax hinzuzufügen, um den Typ von Variablen zu deklarieren, zum Beispiel
var age: int = 42. Wenn wir für Argument-Typ-Hints einen doppelten Doppelpunkt verwenden würden, müssten wir aus Gründen der Konsistenz dieselbe Konvention für zukünftige Syntax verwenden und damit die Hässlichkeit fortsetzen.
Andere Formen neuer Syntax
Einige andere Formen alternativer Syntax wurden vorgeschlagen, z. B. die Einführung eines where Schlüsselworts und Cobra-inspirierte requires Klauseln. Aber diese teilen alle ein Problem mit dem doppelten Doppelpunkt: Sie funktionieren nicht für frühere Versionen von Python 3. Dasselbe würde für einen neuen __future__ Import gelten.
Andere abwärtskompatible Konventionen
Die vorgebrachten Ideen umfassen
- Ein Dekorator, z. B.
@typehints(name=str, returns=str). Das könnte funktionieren, ist aber ziemlich umständlich (eine zusätzliche Zeile und die Argumentnamen müssen wiederholt werden) und weit entfernt von der Eleganz der PEP 3107 Notation. - Stub-Dateien. Wir wollen Stub-Dateien, aber sie sind hauptsächlich nützlich, um Typ-Hints zu vorhandenem Code hinzuzufügen, der sich nicht für das Hinzufügen von Typ-Hints eignet, z. B. 3rd-Party-Pakete, Code, der sowohl Python 2 als auch Python 3 unterstützen muss, und insbesondere Erweiterungsmodule. In den meisten Situationen sind die Annotationen direkt bei den Funktionsdefinitionen viel nützlicher.
- Docstrings. Es gibt eine bestehende Konvention für Docstrings, basierend auf der Sphinx-Notation (
:type arg1: description). Dies ist ziemlich umständlich (eine zusätzliche Zeile pro Parameter) und nicht sehr elegant. Wir könnten auch etwas Neues erfinden, aber die Annotationssyntax ist schwer zu schlagen (da sie genau für diesen Zweck entwickelt wurde).
Es wurde auch vorgeschlagen, einfach eine weitere Version abzuwarten. Aber welches Problem würde das lösen? Es wäre nur Prokrastination.
PEP-Entwicklungsprozess
Ein Live-Entwurf für dieses PEP befindet sich auf GitHub. Es gibt auch ein Issue-Tracker, wo ein Großteil der technischen Diskussion stattfindet.
Der Entwurf auf GitHub wird regelmäßig in kleinen Schritten aktualisiert. Das offizielle PEPS-Repository wird (normalerweise) nur aktualisiert, wenn ein neuer Entwurf an python-dev gesendet wird.
Danksagungen
Dieses Dokument konnte nicht ohne wertvollen Input, Ermutigung und Ratschläge von Jim Baker, Jeremy Siek, Michael Matson Vitousek, Andrey Vlasovskikh, Radomir Dopieralski, Peter Ludemann und dem BDFL-Delegate, Mark Shannon, fertiggestellt werden.
Einflüsse umfassen bestehende Sprachen, Bibliotheken und Frameworks, die in PEP 482 erwähnt werden. Vielen Dank an ihre Schöpfer, in alphabetischer Reihenfolge: Stefan Behnel, William Edwards, Greg Ewing, Larry Hastings, Anders Hejlsberg, Alok Menghrajani, Travis E. Oliphant, Joe Pamer, Raoul-Gabriel Urma und Julien Verlaguet.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0484.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT