[PYTHON] [Übersetzung] PEP 0484 --Tipps

Dieser Artikel ist eine Übersetzung des in Python 3.5 eingeführten Typhinweises PEP 0484.

Wir hatten nicht genug Zeit, um es zu übersetzen. Wenn Sie also eine Fehlübersetzung oder eine geeignetere Übersetzung haben, teilen Sie uns dies bitte in einer Bearbeitungsanfrage mit.

PEP 0484 - Tipp geben

PEP: 484
Titel: Tipp Hinweis
Autor: Guido van Rossum <guido at python.org>, Jukka Lehtosalo <jukka.lehtosalo at iki.fi>, Łukasz Langa <lukasz at langa.pl>
BDFL-Delegation: Mark Shannon
Discussions-To: Python-Dev <python-dev at python.org>
Status: Genehmigung
Art: Standardisierungsprozess
Erstellungsdatum: 29. September 2014
Beitragsgeschichte: 16. Januar 2015, 20. März 2015, 17. April 2015, 20. Mai 2015, 22. Mai 2015
Auflösung: https://mail.python.org/pipermail/python-dev/2015-May/140104.html

Übersicht

PEP 3107 führte die Syntax für Funktionsanmerkungen ein, deren Semantik jedoch absichtlich undefiniert war. Heutzutage gibt es viele Anwendungen von Drittanbietern für die statische Typanalyse, und die Community kann von standardisiertem Vokabular und grundlegenden Tools in Standardbibliotheken profitieren.

In diesem PEP werden Konventionen für Situationen eingeführt, in denen vorläufige Module und Anmerkungen nicht verfügbar sind, um standardisierte Definitionen und Tools bereitzustellen.

Dieses PEP verhindert immer noch nicht explizit andere Verwendungen von Anmerkungen. Beachten Sie außerdem, dass die Einhaltung dieser Spezifikation keine spezielle Verarbeitung von Anmerkungen erfordert (oder verbietet). Es bringt einfach eine bessere Koordination, da PEP 333 zum Webframework gegangen ist.

Beispielsweise deklariert die folgende einfache Funktion ihre Argument- und Rückgabetypen mit Anmerkungen.

def greeting(name: str) -> str:
    return 'Hello ' + name

Während diese Anmerkungen zur Laufzeit als reguläre Attribute "annotations" referenziert werden können, führen sie zur Laufzeit keine Typprüfung durch. Stattdessen wird in diesem Vorschlag die Existenz eines separaten Offline-Prüfers vorausgesetzt. Benutzer können den Quellcode freiwillig mit einer solchen Typprüfung überprüfen. Grundsätzlich wirken diese Typprüfer als sehr leistungsfähige Linters. (Natürlich könnten ähnliche Prüfer verwendet werden, um Design By Contract für einzelne Benutzer und die JIT-Optimierung zur Laufzeit durchzusetzen, aber diese Tools befinden sich noch auf praktischem Niveau. nicht.)

Dieser Vorschlag ist stark von mypy [mypy] inspiriert. Zum Beispiel wird der Typ der "Sequenz vom ganzzahligen Typ" als "Sequenz [int]" beschrieben. Es ist nicht erforderlich, der Sprache mithilfe von eckigen Klammern eine neue Syntax hinzuzufügen. In dem hier vorgestellten Beispiel wird der benutzerdefinierte Typ "Sequenz" verwendet, der aus dem reinen Python-Typisierungsmodul importiert wurde. Notationen wie "Sequence [int]" funktionieren zur Laufzeit, indem sie "getitem ()" in der Metaklasse implementieren (ihre Signatur ist jedoch hauptsächlich für Offline-Prüfer bestimmt).

Das Typsystem unterstützt Vereinigungstypen, generische Typen und spezielle Typen, die mit allen Typen "Beliebig" konsistent (oder gegenseitig zuweisbar) sind. Diese letztere Funktion ist von der Idee der schrittweisen Eingabe geerbt. Die schrittweise Eingabe und das gesamte Typsystem sind in PEP 483 beschrieben.

Andere Methoden, die wir ausgeliehen haben, oder andere Methoden zum Vergleichen oder Gegenüberstellen sind in PEP 482 zusammengefasst.

Begründung und Zweck

PEP 3107 wurde hinzugefügt, um beliebige Anmerkungen als Teil der Funktionsdefinition zu unterstützen. Die Annotation hatte zu diesem Zeitpunkt keine besondere Bedeutung, hatte jedoch immer den potenziellen Zweck, sie für Typhinweise [[gvr-artima]](https: //www.python) zu verwenden. org / dev / peps / pep-0484 / # gvr-artima). Die Typprüfung wird als erster Anwendungsfall in PEP 3107 erwähnt.

Dieses PEP soll eine standardisierte Syntax für Typanmerkungen bereitstellen. Es eröffnet die Möglichkeiten von Python-Code für einfaches statisches Parsen und Refactoring, potenzielle Laufzeit-Typprüfung und (möglicherweise in einem bestimmten Kontext) Codegenerierung mithilfe von Typinformationen.

Der wichtigste dieser Zwecke ist die statische Analyse. Dies umfasst die Unterstützung von Offline-Prüfern wie mypy und die von der IDE für die Code-Vervollständigung und das Refactoring verwendete Standardnotation.

Unbeabsichtigt

Das vorgeschlagene Typisierungsmodul enthält Komponenten für die Laufzeitprüfung, insbesondere die Funktion "get_type_hints ()", aber zur Implementierung bestimmter Laufzeitprüfungsfunktionen (z. B. Dekoratoren und Sie müssen ein Paket eines Drittanbieters entwickeln (unter Verwendung von Metaklassen). Die Verwendung von Typhinweisen zur Optimierung der Leistung bleibt eine Herausforderung für die Leser dieses PEP.

Lassen Sie uns auch Folgendes hervorheben. ** Python ist immer noch eine dynamisch typisierte Sprache. Python-Autoren möchten keine Typhinweise benötigen (auch nicht als Konventionen). ** **.

Annotation Semantics

Jede nicht kommentierte Funktion sollte von allen Typprüfern als möglichst generisch behandelt oder ignoriert werden. Funktionen mit dem Dekorator "@ no_type_check" oder dem Kommentar "# type: ignore" sollten so behandelt werden, dass sie keine Anmerkungen enthalten.

Es wird empfohlen, aber nicht erforderlich, dass die überprüfte Funktion alle Argumente enthält und Anmerkungen zurückgibt. Die Standardanmerkung für geprüfte Funktionsargumente und Rückgabetypen ist vom Typ "Beliebig". Die Ausnahme besteht darin, dass die ersten Argumente von Instanz- und Klassenmethoden nicht mit Anmerkungen versehen werden müssen. Es wird angenommen, dass die Instanzmethode den Typ der Klasse hat, zu der die Instanzmethode gehört, und dass die Klassenmethode den Typobjekttyp hat, der dem Klassenobjekt entspricht, zu dem die Klassenmethode gehört. Beispielsweise ist das erste Argument einer Instanzmethode der Klasse "A" implizit vom Typ "A". Klassenmethoden können nicht den genauen Typ des ersten Arguments in der verfügbaren Typnotation darstellen.

(Beachten Sie, dass der Rückgabetyp von "init" wie "-> Keine "mit Anmerkungen versehen werden sollte. Der Grund ist subtil. Wenn" init "eine Anmerkung des Rückgabetyps" -> Keine "ist Sollte eine nicht kommentierte "init" -Methode ohne Argumente typgeprüft werden, wenn dies der Fall ist? Anstatt diese Mehrdeutigkeit zu belassen oder eine Ausnahme von dieser Ausnahme einzuführen, "" init` sagt, dass es eine Rückgabeanmerkung haben sollte, wodurch sein Standardverhalten anderen Methoden ähnelt.)

Von der Typprüfung wird erwartet, dass sie prüft, ob der Inhalt der zu prüfenden Funktion mit der angegebenen Anmerkung übereinstimmt. Anmerkungen werden auch verwendet, um die Gültigkeit von Aufrufen zu überprüfen, die in anderen überprüften Funktionen angezeigt werden.

Von Typprüfern wird erwartet, dass sie versuchen, so viele Informationen wie nötig zu erraten. Die Mindestanforderung besteht darin, die integrierten Dekoratoren "@property, @ staticmethod" und "@ classmethod" zu handhaben.

Syntax der Typdefinition

Die Syntax nutzt Annotationen im Stil von PEP 3107 sowie viele in den folgenden Abschnitten beschriebene Erweiterungen. Die Grundform besteht darin, Typhinweise zu verwenden, indem Funktionsanmerkungs-Slots mit Klassen gefüllt werden.

def greeting(name: str) -> str:
    return 'Hello ' + name

Dies erklärt, dass der erwartete Typ des "name" -Arguments "str" ist. In ähnlicher Weise ist der erwartete Rückgabetyp "str".

Ausdrücke, deren Typ ein Subtyp des Typs eines bestimmten Arguments ist, sind ebenfalls als dieses Argument akzeptabel.

Zulässiger Typhinweis

Typhinweise können integrierte Klassen (einschließlich der in Standardbibliotheken oder Erweiterungsmodulen von Drittanbietern definierten), abstrakte Basisklassen, im Modul "Typen" definierte Typen und benutzerdefinierte Klassen (Standardbibliotheken oder Module von Drittanbietern) sein. (Einschließlich der in definierten).

Obwohl Anmerkungen im Allgemeinen das beste Format für Typhinweise sind, ist es möglicherweise besser, die Typhinweise in speziellen Kommentaren oder in einer separaten Stub-Datei darzustellen. (Siehe Beispiel unten.)

Die Annotation muss ein gültiger Ausdruck sein, der ausnahmslos ausgewertet wird, wenn die Funktion definiert ist (Vorwärtsreferenzen siehe unten).

Anmerkungen sollten einfach gehalten werden, da statische Analysewerkzeuge ihre Werte sonst möglicherweise nicht interpretieren können. Beispielsweise ist es unwahrscheinlich, dass dynamisch berechnete Typen verständlich sind. (Dies ist absichtlich eine etwas vage Anforderung. Aufgrund der Diskussion können bestimmte zusätzliche oder Ausnahmespezifikationen zu zukünftigen Versionen dieses PEP hinzugefügt werden.)

Zusätzlich zu den oben genannten sind spezielle Konstruktoren verfügbar, die unten definiert sind: Alle abstrakten Basisklassen, die in "None", "Any", "Union", "Tuple", "Callable", "Typing" verfügbar gemacht werden Und Alternativen für konkrete Klassen (z. B. "Sequenz" und "Dikt"), Typvariablen und Typaliasnamen.

Alle neu eingeführten Namen (wie "Any" und "Union"), die zur Unterstützung der in den folgenden Abschnitten beschriebenen Funktionen verwendet werden, werden vom "Typing" -Modul bereitgestellt.

Verwenden von None

Bei Verwendung in Typhinweisen wird der Ausdruck "Keine" als äquivalent zu "Typ (Keine)" angesehen.

Alias eingeben

Typ-Aliase werden einfach durch Zuweisen von Variablen definiert.

Url = str

def retry(url: Url, retry_count: int) -> None:
    ...

Hierbei ist zu beachten, dass empfohlen wird, den Anfang der Zeichen im Alias groß zu schreiben. Dies liegt daran, dass Aliase benutzerdefinierte Typen darstellen, die normalerweise so geschrieben werden (wie benutzerdefinierte Klassen).

Typ-Aliase können so komplex sein wie Typ-Hinweise in Anmerkungen. Alles, was als Typhinweis akzeptabel ist, ist als Typalias akzeptabel.

from typing import TypeVar, Iterable, Tuple

T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]

def inproduct(v: Vector) -> T:
    return sum(x*y for x, y in v)

Dies entspricht dem folgenden Code.

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)

Aufrufbare Objekte (https://www.python.org/dev/peps/pep-0484/#id17)

Frameworks, die Rückruffunktionen mit einer bestimmten Signatur empfangen, können "Callable [[Arg1Type, Arg2Type], ReturnType]" verwenden, um Typhinweise zu erstellen. Hier ist ein Beispiel.

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

Sie können den Rückgabetyp eines aufrufbaren Objekts deklarieren, ohne eine Aufrufsignatur anzugeben, indem Sie die Argumentliste durch eine wörtliche Ellipse (drei Punkte) ersetzen.

def partial(func: Callable[..., str], *args) -> Callable[..., str]:
    # Body

Beachten Sie, dass vor oder nach den Auslassungspunkten keine eckigen Klammern stehen. In diesem Fall gibt es keine Einschränkungen für die Argumente der Rückruffunktion (und Schlüsselwortargumente sind akzeptabel).

Derzeit gibt es keinen Mechanismus zum Angeben von Schlüsselwortargumenten für "Callable", da die Verwendung von Schlüsselwortargumenten in Rückruffunktionen nicht als allgemeiner Anwendungsfall erkannt wird. Ebenso gibt es keinen Mechanismus zum Angeben eines bestimmten Typs von Argumenten variabler Länge in einer Rückrufsignatur.

isinstance (x, typing.Callable) ersetztisinstance (x, collection.abc.Callable)weil typing.Callable zwei Rollen als Ersatz für collection.abc.Callable hat. Es wird durch Verzögerung implementiert. Isinstance (x, typing.Callable [...]) wird jedoch nicht unterstützt.

Generika

Typinformationen zu Objekten in einem Container können nicht auf die übliche Weise statisch vom Typ abgeleitet werden. Infolgedessen wurde die abstrakte Basisklasse erweitert, um Array-Indizes zu unterstützen und den erwarteten Typ des Containerelements anzugeben. Hier ist ein Beispiel.

from typing import Mapping, Set

def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None:
    ...

Generika werden mit einer neuen Factory namens "TypeVar" im "Typing" -Modul parametriert. Hier ist ein 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 besteht der Vertrag darin, dass der Rückgabewert mit den in der Sammlung enthaltenen Elementen übereinstimmt.

Der Ausdruck "TypeVar ()" muss immer direkt einer Variablen zugewiesen werden (sollte nicht als Teil eines größeren Ausdrucks verwendet werden). Das an TypeVar () übergebene Argument muss eine Zeichenfolge sein, die dem Namen der Variablen entspricht, der es zugewiesen ist. Definieren Sie Typvariablen nicht neu.

TypeVar unterstützt parametrisierte Typen, die einen bestimmten Satz möglicher Typen einschränken. Beispielsweise können Sie eine Typvariable definieren, die nur "str" und "bytes" verarbeitet. Standardmäßig behandeln Typvariablen alle möglichen Typen. Dies ist ein Beispiel für eine Typvariableneinschränkung.

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y

Die Funktion "concat" kann entweder mit zwei Argumenten vom Typ "str" oder zwei Argumenten vom Typ "bytes" aufgerufen werden, jedoch mit einer Mischung aus Argumenten vom Typ "str" und Argumenten vom Typ "bytes". Es kann nicht aufgerufen werden.

Es müssen mindestens zwei Einschränkungen vorhanden sein, falls vorhanden. Sie können nicht nur eine Einschränkung angeben.

Die durch die Typvariable eingeschränkten Untertypen des Typs sollten im Kontext der Typvariablen als ihre jeweiligen explizit dargestellten Basistypen behandelt werden. Betrachten Sie dieses Beispiel.

class MyStr(str):
    ...

x = concat(MyStr('apple'), MyStr('pie'))

Dieser Aufruf ist gültig, aber seine Typvariable "AnyStr" wird auf "str" anstelle von "MyStr" gesetzt. Der Typ, der als der Rückgabewert abgeleitet wird, der "x" zugewiesen ist, ist tatsächlich "str".

Außerdem ist "Any" ein gültiger Wert für alle Typvariablen. Folgendes berücksichtigen:

def count_truthy(elements: List[Any]) -> int:
    return sum(1 for elem in elements if element)

Dies entspricht dem Entfernen der generischen Notation und dem einfachen Aussprechen von "elements: List".

Benutzerdefinierter generischer Typ

Mit der Basisklasse "Generic" können Sie eine benutzerdefinierte Klasse als generischen Typ definieren. Hier ist ein Beispiel.

from typing import TypeVar, Generic

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 eine Klasse LoggedVar, die einen Typparameter T akzeptiert. Es aktiviert auch T als Typ innerhalb der Klasse.

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)

Generische Typen nehmen eine beliebige Anzahl von Typvariablen und beschränken sie. Der folgende Code ist gültig.

from typing import TypeVar, Generic
...

T = TypeVar('T')
S = TypeVar('S')

class Pair(Generic[T, S]):
    ...

Die einzelnen Typvariablen, die an "Generic" übergeben werden, müssen getrennt sein. Dies macht den folgenden Code ungültig.

from typing import TypeVar, Generic
...

T = TypeVar('T')

class Pair(Generic[T, T]):   # INVALID
    ...

Mehrfachvererbung kann für "Allgemein" verwendet werden.

from typing import TypeVar, Generic, Sized

T = TypeVar('T')

class LinkedList(Sized, Generic[T]):
    ...

Wenn Sie eine generische Klasse ohne Angabe eines Typparameters unterordnen, wird davon ausgegangen, dass Sie für jedes Positionsargument "Any" angegeben haben. Im folgenden Beispiel ist "MyIterable" kein generischer Typ, sondern erbt implizit von "Iterable [Any]".

from typing import Iterable

class MyIterable(Iterable): # Same as Iterable[Any]
    ...

Generische Metaklassen werden nicht unterstützt.

Generische Klasseninstanziierung und Typeliminierung

Generische Typen wie "List" und "Sequence" können nicht instanziiert werden. Benutzerdefinierte Klassen, die von diesen generischen Typen abgeleitet sind, können jedoch instanziiert werden. Stellen Sie sich eine Node-Klasse vor, die von Generic [T] erbt.

from typing import TypeVar, Generic

T = TypeVar('T')

class Node(Generic[T]):
    ...

Es gibt jetzt zwei Möglichkeiten, diese Klasse zu instanziieren. Der Typ, auf den die Typprüfung verweist, hängt davon ab, welches Format Sie verwenden. Die erste Möglichkeit besteht darin, den Wert des Typparameters explizit zu übergeben. Dies überschreibt alle vom Typprüfer durchgeführten Typinferenzen.

x = Node[T]() # The type inferred for x is Node[T].
y = Node[int]() # The type inferred for y is Node[int].

Wenn der Typ nicht explizit angegeben ist, kann die Typprüfung ableiten. Betrachten Sie den folgenden Code.

x = Node()

Der abgeleitete Typ ist "Node [Any]". Es gibt nicht genügend Kontext, um auf einen genaueren Typ schließen zu können. Alternativ akzeptiert die Typprüfung diese Zeile möglicherweise nicht und erfordert eine explizite Anmerkung wie folgt:

x = Node() # type: Node[int] # Inferred type is Node[int].

Typprüfer mit stärkeren Typinferenzen untersuchen, wie "x" in der Datei verwendet wird, und selbst wenn keine explizite Typanmerkung gefunden wird, geben Sie die Inferenz genauer ein, wie "Node [int]". Ich werde versuchen. Aber vielleicht ist es unmöglich, diese Art von Typinferenz in allen Fällen zum Laufen zu bringen. Python-Programme können zu dynamisch sein.

Dieser PEP enthält keine Details zur Funktionsweise der Typinferenz. Wir erlauben verschiedenen Werkzeugen, verschiedene Ansätze auszuprobieren. Zukünftige Versionen können klarere Regeln festlegen.

Der Typ bleibt zur Laufzeit nicht erhalten. Die Klasse von "x" ist in allen Fällen einfach "Knoten". Dieses Verhalten wird als "Typeliminierung" bezeichnet. Dies ist eine übliche Praxis für Sprachen mit Generika (z. B. Java und TypeScript).

Beliebiger generischer Typ als Basisklasse

Generic [T] ist nur als Basisklasse gültig. Dies ist nicht unbedingt ein Typ. Benutzerdefinierte generische Typen in "LinkedList [T]" im obigen Beispielcode, integrierte generische Typen und abstrakte Basisklassen wie "List [T]" und "Iterable [T]" können jedoch auch Typen sein. Es gilt auch als Basisklasse. Beispielsweise können Sie eine Unterklasse von "Dict" mit speziellen Typargumenten definieren.

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 ein Subtyp von Dict [str, List [Node]].

Wenn die generische Basisklasse eine Typvariable als Typargument hat, wird die definierte Klasse generisch. Beispielsweise können Sie eine generische "LinkedList" -Klasse definieren, die ein wiederholbares Containerobjekt ist.

from typing import TypeVar, Iterable, Container

T = TypeVar('T')

class LinkedList(Iterable[T], Container[T]):
    ...

Jetzt ist LinkedList [int] ein gültiger Typ. Beachten Sie, dass Sie "T" in der Liste der Basisklassen mehrmals verwenden können, wenn Sie "T" in "Generic [...]" nicht mehrmals verwenden.

Beachten Sie auch das folgende Beispiel.

from typing import TypeVar, Mapping

T = TypeVar('T')

class MyDict(Mapping[str, T]):
    ...

In diesem Fall nimmt MyDict einen einzelnen Parameter T an.

Abstrakter generischer Typ

Die von "Generic" verwendete Metaklasse ist eine Unterklasse von "abc.ABCMeta". Generische Klassen werden zu abstrakten Basisklassen, indem abstrakte Methoden oder Eigenschaften eingeschlossen werden. Darüber hinaus können generische Klassen mehrere abstrakte Basisklassen als Basisklassen ohne Metaklassenkonflikte haben.

Typvariable mit Obergrenze

Typvariablen können mit bound = type überbunden werden. Dies bedeutet, dass der tatsächliche Typ, der die Typvariable (explizit oder implizit) ersetzt, eine Unterklasse des Grenztyps sein muss. Ein häufiges Beispiel ist die Definition eines vergleichbaren Typs, der gut genug funktioniert, um die häufigsten Fehler abzufangen.

from typing import TypeVar

class Comparable(metaclass=ABCMeta):
    @abstractmethod
    def __lt__(self, other: Any) -> bool:
        ...
    ... # __gt__ etc. as well

CT = TypeVar('CT', bound=Comparable)

def min(x: CT, y: CT) -> CT:
    if x < y:
        return x
    else:
        return y

min(1, 2) # ok, return type int
min('x', 'y') # ok, return type str

(Beachten Sie, dass dies nicht immer ideal ist. Beispielsweise führt "min (" x ", 1)" zu einem Laufzeitfehler, aber die Typprüfung leitet einfach "Comparable" als Rückgabetyp ab. Leider müssen wir ein leistungsfähigeres und komplexeres Konzept einführen, den F-gebundenen Polymorphismus, um dies anzugehen. In Zukunft werden wir dieses Konzept möglicherweise überdenken.)

Die Obergrenze kann nicht mit Typbeschränkungen kombiniert werden (wie das im vorherigen Beispiel verwendete "AnyStr"). Typeinschränkungen stellen sicher, dass der abgeleitete Typ \ _ genau \ _ mit einem der eingeschränkten Typen übereinstimmt. Die Obergrenze erfordert andererseits nur, dass der tatsächliche Typ eine Unterklasse des Grenztyps ist.

Co-Modifikation und Anti-Modifikation

Betrachten Sie die Klasse "Mitarbeiter" und ihre Unterklasse "Manager". Angenommen, Sie haben jetzt eine Funktion mit Argumenten, die mit "List [Employee]" versehen sind. Sollte es erlaubt sein, diese Funktion mit einer Variablen vom Typ "List [Manager]" als Argument aufzurufen? Viele würden mit "Ja, natürlich" antworten, ohne über die logischen Konsequenzen nachzudenken. Typprüfer sollten solche Aufrufe jedoch ablehnen, es sei denn, sie wissen mehr über die Funktion. Die Funktion kann ihrer Liste eine Empoyee-Instanz hinzufügen. Es verletzt den Variablentyp des Aufrufers.

Es stellt sich heraus, dass sich ein solches Argument verhältnismäßig verhält. Die intuitive Antwort (richtig, wenn diese Funktion das Argument nicht ändert!) Fordert ein Argument an, das sich \ _ kovariant verhält. Eine lange Einführung in diese Konzepte finden Sie bei Wikipedia [Wiki-Varianz]. Lassen Sie uns hier kurz erklären, wie Sie das Verhalten der Typprüfung steuern.

Standardmäßig wird angenommen, dass Typvariablen \ _invariant \ _ (invariant) sind. Das bedeutet, dass Argumente für mit Anmerkungen versehene Argumente, wie z. B. der Typ "List [Employee]", genau mit der Typanmerkung übereinstimmen müssen. Unterklassen und Oberklassen von Typparametern (in diesem Beispiel "Mitarbeiter") sind nicht zulässig.

Um einen Containertyp zu deklarieren, der kovariante Typprüfungen akzeptiert, deklarieren Sie die Typvariable mit covariant = True. Übergeben Sie "contravariant = True", wenn kontravariantes Verhalten erforderlich ist (selten). Sie können höchstens eines davon bestehen.

Ein typisches Beispiel ist die Definition einer nicht wiederbeschreibbaren (oder schreibgeschützten) Containerklasse.

from typing import TypeVar, Generic, Iterable, Iterator

T = TypeVar('T', covariant=True)

class ImmutableList(Generic[T]):
    def __init__(self, items: Iterable[T]) -> None:
        ...
    def __iter__(self) -> Iterator[T]:
        ...
    ...

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

Alle schreibgeschützten Sammlungsklassen im Modul "Typing" (z. B. "Mapping" und "Sequence") werden mit kovarianten Typvariablen deklariert. Variablensammlungsklassen (z. B. "MutableMapping" und "MutableSequence") werden unter Verwendung regulärer unveränderlicher Typvariablen definiert. Ein Beispiel für eine kontravariante Typvariable ist der Typ "Generator". Der Argumenttyp von "send ()" ist kontravariant (siehe unten).

Hinweis: Die Variation wirkt sich auf die generischen Typparameter aus. Normale Parameter werden nicht beeinflusst. Das folgende Beispiel ist beispielsweise in Ordnung.

from typing import TypeVar

class Employee:
    ...

class Manager(Employee):
    ...

E = TypeVar('E', bound=Employee)  # Invariant

def dump_employee(e: E) -> None:
    ...

dump_employee(Manager())  # OK

Numerische Hierarchie

PEP 3141 definiert eine numerische Hierarchie von Python. Das Modul "Zahlen" der Standardbibliothek implementiert die entsprechenden abstrakten Basisklassen ("Nummer", "Komplex", "Real", "Rational", "Integral"). Diese abstrakten Basisklassen haben einige Herausforderungen, aber die eingebauten konkreten numerischen Klassen "complex", "float" und "int" werden überall verwendet (insbesondere die beiden letzteren :-).

Anstatt vom Benutzer zu verlangen, "Importnummern" zu schreiben und so etwas wie "Nummern.Float" zu verwenden, schlägt dieser PEP eine einfache Verknüpfung vor, die fast den gleichen Effekt hat: Annotieren Sie, wenn ein Argument den Typ "float" hat. Argumente vom Typ "int" sind ebenfalls zulässig, wenn sie erledigt sind. Wenn Sie einen "komplexen" Typ haben, ist für das mit Anmerkungen versehene Argument ein Argument vom Typ "float" oder "int" zulässig. Es kann keine Klassen verarbeiten, die die entsprechende abstrakte Basisklasse oder die Klasse "Fractions.Fraction" implementieren, aber ich denke, solche Anwendungsfälle sind sehr selten.

Bytetyp

Es gibt drei integrierte Klassen für Arrays von Bytes: "Bytes", "Bytearray" und "Memoryview" (die vom "Array" -Modul bereitgestellten Klassen werden nicht gezählt). Natürlich teilen "Bytes" und "Bytearray" viel Verhalten (aber nicht alle, zum Beispiel "Bytearray" können geändert werden).

Eine gemeinsame Basisklasse namens "ByteString" ist in "collection.abc" definiert, und der entsprechende Typ existiert auch in "typing", aber da es normalerweise Funktionen gibt, die (jeden) Bytetyp akzeptieren. Es wäre mühsam, überall "typing.ByteString" schreiben zu müssen. Daher ist als Verknüpfung ähnlich der integrierten numerischen Klasse auch ein Typ "bytearray" oder "memoryview" zulässig, wenn das Argument mit einem Typ "bytes" versehen ist. (Wiederholen Sie, es gibt Situationen, in denen dies nicht funktioniert, aber ich denke, dass es in der Praxis selten ist.)

Siehe

Wenn ein Typhinweis einen Namen enthält, der noch nicht definiert wurde, kann die Definition als Zeichenfolgenliteral dargestellt werden. Der durch das Zeichenfolgenliteral dargestellte Name wird später aufgelöst.

Eine häufige Situation, in der dies geschieht, ist das Definieren einer Containerklasse. Bei Containerklassen wird die von Ihnen definierte Klasse in den Signaturen einiger Methoden angezeigt. Der folgende Code (die ersten Zeilen einer einfachen Binärbaumimplementierung) funktioniert beispielsweise nicht.

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

Um dies zu korrespondieren, schreiben Sie wie folgt.

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

Das Zeichenfolgenliteral muss einen gültigen Python-Ausdruck enthalten (dh "compile (lit,", "eval") wird zu einem gültigen Codeobjekt), wenn das Modul vollständig geladen ist. Es muss fehlerfrei auswerten können. Der lokale und der globale Namespace müssen bei der Auswertung mit dem Namespace identisch sein, in dem die Standardargumente für dieselbe Funktion ausgewertet werden.

Darüber hinaus muss der Ausdruck als gültiger Typhinweis analysiert werden können. Dies wird beispielsweise durch die oben im Abschnitt Tipps für akzeptable Typen (https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints) genannten Regeln eingeschränkt.

Es ist erlaubt, String-Literale als _Teil _ von Typhinweisen zu verwenden. Zum Beispiel

class Tree:
    ...
    def leaves(self) -> List['Tree']:
        ...

Ein häufiger Anwendungsfall für Vorwärtsreferenzen ist beispielsweise, wenn das Django-Modell für Signaturen erforderlich ist. Jedes Modell befindet sich normalerweise in einer separaten Datei und verfügt über Methoden, die Argumente mit Typen annehmen, die sich auf das andere Modell beziehen. Aufgrund der zirkulären Importlösung in Python 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

Angenommen, die Hauptleitung wird zuerst importiert, schlägt dies mit einem ImportError in der Zeile from models.a import A in models / b.py fehl. Dies liegt daran, dass b.py aus models / a.py importiert wurde, bevor Klasse A definiert wurde. Die Lösung besteht darin, nur Module zu importieren und das Modell im Format \ _module \ _. \ _ Class \ _ 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

Unionstypen

Da es üblich ist, eine begrenzte Anzahl erwarteter Typen für ein einzelnes Argument zu erhalten, wird eine spezielle Fabrik namens "Union" bereitgestellt. Hier ist ein Beispiel.

from typing import Union

def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
    if isinstance(e, Employee):
        e = [e]
    ...

Der durch "Union [T1, T2, ...]" dargestellte Typ ist das Ergebnis der "issubclass" -Prüfung auf "T1" und seinen willkürlichen Subtyp, "T2" und seinen willkürlichen Subtyp usw. Es wird "wahr" sein.

Ein häufiger Anwendungsfall für den direkten Summentyp ist der optionale Typ. Standardmäßig ist "Keine" für jeden Typ ungültig, es sei denn, Sie geben beim Definieren der Funktion den Standardwert "Keine" an. Hier ist ein Beispiel.

def handle_employee(e: Union[Employee, None]) -> None:
    ...

Sie können "Optional [T1]" als vereinfachte Darstellung von "Union [T1, None]" schreiben. Beispielsweise entspricht der vorherige Code dem folgenden Code.

from typing import Optional

def handle_employee(e: Optional[Employee]) -> None:
    ...

Wenn der Standardwert "Keine" ist, wird er automatisch als optionaler Typ betrachtet. Zum Beispiel

def handle_employee(e: Employee = None):
    ...

Dies entspricht dem folgenden Code.

def handle_employee(e: Optional[Employee] = None) -> None:
    ...

Any type

Any ist ein besonderer Typ. Alle Typen sind Untertypen von "Any". Gleiches gilt für das eingebaute "Objekt". Bei statischen Prüfern sind diese jedoch völlig unterschiedlich.

Wenn der Typ eines Wertes "Objekt" ist, verweigert die Typprüfung fast jede Operation für diesen Wert. Das Zuweisen dieses Werts zu einer Variablen eines spezielleren Typs (oder das Verwenden dieses Werts als Rückgabewert) führt zu einem Typfehler. Wenn andererseits ein Wert vom Typ "Beliebig" ist, erlaubt die Typprüfung jetzt alle Operationen an diesem Wert, indem einer Variablen mit eingeschränkterem Typ ein Wert vom Typ "Beliebig" zugewiesen (oder zugewiesen) wird. Sie können diesen Wert als Rückgabewert verwenden.

Version und Plattform überprüfen

Die Typprüfung ist erforderlich, um einfache Versions- und Plattformprüfungen durchführen zu können. Hier ist ein Beispiel.

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 der Prüfer verschleierten Code wie "" verarbeitet. Join (umgekehrt (sys.platform) == "xunil" ".

Standardargumentwert

In Stubs kann es nützlich sein, Argumente mit Standardwerten deklarieren zu können, ohne tatsächliche Standardwerte anzugeben. Hier ist ein Beispiel.

def foo(x: AnyStr, y: AnyStr = ...) -> AnyStr:
    ...

Was sollte der Standardwert sein? Entweder `` ", b" "oderNone erfüllt die Typbeschränkung nicht (tatsächlich ändert None _ seinen Typ in Optional [AnyStr] `_ ).

In diesen Fällen können Sie den Standardwert als Literalellipse angeben. Mit anderen Worten, das obige Beispiel repräsentiert genau das, was Sie schreiben möchten.

Kompatibilität mit der Verwendung anderer Funktionsanmerkungen

Es gibt viele vorhandene oder potenzielle Anwendungsfälle für Funktionsanmerkungen, die mit Typhinweisen nicht kompatibel sind. Solche Dinge können statische Prüfer verwirren. Die Typ-Hinweis-Annotation führt jedoch zur Laufzeit nichts aus (sie wertet lediglich den Annotationsausdruck aus und behält die Annotation im Attribut __annotations__ des Funktionsobjekts bei). Dies macht das Programm nicht bösartig. Die Typprüfung gibt nur die falsche Warnung oder den falschen Fehler aus.

Verwenden Sie einige der unten beschriebenen Methoden, um zu vermeiden, dass Sie Typhinweise auf Teile Ihres Programms anwenden.

Weitere Einzelheiten finden Sie in späteren Abschnitten.

Letztendlich könnte es eine gute Idee sein, die annotationsabhängige Schnittstelle auf einen anderen Mechanismus (z. B. einen Dekorateur) umzustellen, um maximale Kompatibilität mit der Offline-Überprüfung zu gewährleisten. Allerdings gibt es in Python 3.5 keinen solchen Druck. Siehe auch die ausführliche Diskussion über abgelehnte Alternativen (https://www.python.org/dev/peps/pep-0484/#rejected-alternatives) weiter unten.

Kommentar eingeben

Dieses PEP fügt keine erstklassige Syntax zum expliziten Markieren einer Variablen als bestimmten Typ hinzu. Kommentare in den folgenden Formaten können verwendet werden, um die Typinferenz in komplexen Fällen zu unterstützen.

x = []   # type: List[Employee]
x, y, z = [], [], []  # type: List[int], List[int], List[str]
x, y, z = [], [], []  # type: (List[int], List[int], List[str])
x = [
   1,
   2,
]  # type: List[int]

Der Typkommentar muss sich in der letzten Zeile der Anweisung befinden, die die Variablendeklaration enthält. Typkommentare können auch unmittelbar nach dem Doppelpunkt in den Anweisungen "with" und "for" eingefügt werden.

Beispiele für Typkommentare für "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 einen Anfangswert anzugeben. Dies kann mithilfe der wörtlichen Auslassungspunkte erfolgen.

from typing import IO

stream = ...  # type: IO[str]

Für Nicht-Stub-Code gibt es ähnliche Sonderfälle.

from typing import IO

stream = None # type: IO[str]

Die Typprüfung sollte diesen Code nicht als Fehler betrachten (obwohl der Wert "None" nicht mit dem angegebenen Typ übereinstimmt) und den abgeleiteten Typ in "Optional [...]" ändern Nein (trotz der Regel, die dies für das mit Anmerkungen versehene Argument und den Standardwert "Keine" tut). Hierbei wird davon ausgegangen, dass anderer Code für die Zuweisung des entsprechenden Werttyps zu dieser Variablen verantwortlich ist. Und Sie können davon ausgehen, dass diese Variable überall dort, wo Sie sie verwenden, den angegebenen Typ hat.

Der Kommentar "# type: ignore" muss in der Zeile stehen, die auf den Fehler verweist.

import http.client
errors = {
    'not_found': http.client.NOT_FOUND  # type: ignore
}

# type: ignore Wenn ein Kommentar allein in einer Zeile vorhanden ist, wird die Typprüfung von der Zeile mit dem Kommentar zum Rest der Datei deaktiviert.

Wenn sich Typhinweise im Allgemeinen als nützlich erweisen, wird die Typvariablensyntax möglicherweise in zukünftigen Python-Versionen bereitgestellt.

Besetzung

In einigen Fällen benötigt die Typprüfung möglicherweise verschiedene Arten von Hinweisen: Der Programmierer weiß möglicherweise, dass ein Ausdruck ein eingeschränkterer Typ ist, als die Typprüfung 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 Typprüfer können möglicherweise nicht auf den Typ "a [index]" schließen, sondern auf "object" oder "Any". Der Programmierer weiß jedoch, dass die Zeichenfolge korrekt ist (wenn der Code dort ankommt). cast (t, x) teilt dem Typprüfer mit, dass er sicher ist, dass der Typ von x`` t ist. Zur Laufzeit gibt die Besetzung den Ausdruck immer unverändert zurück. Es überprüft seinen Typ nicht und konvertiert ihn nicht in seinen Wert.

Casts unterscheiden sich von Typkommentaren (siehe vorherigen Abschnitt). Bei Verwendung von Typkommentaren muss die Typprüfung weiterhin überprüfen, ob der abgeleitete Typ mit dem angegebenen Typ übereinstimmt. Bei Verwendung von cast sollte die Typprüfung dem Programmierer wahllos vertrauen. Außerdem werden Casts in Ausdrücken verwendet, während Typkommentare in Zuweisungen angewendet werden.

Stub-Datei

Eine Stub-Datei ist eine Datei, die Typhinweise enthält und nur von der Typprüfung verwendet wird, nicht zur Laufzeit. Es gibt verschiedene Anwendungsfälle für Stub-Dateien.

Die Stub-Datei verwendet dieselbe Syntax wie ein reguläres Python-Modul. Es gibt eine Funktion des Typing-Moduls, die nur mit Stub-Dateien verwendet werden kann. Es ist der in den folgenden Abschnitten beschriebene Dekorator "@ overload".

Die Typprüfung sollte nur die Funktionssignatur in der Stub-Datei überprüfen. Es wird empfohlen, dass der Funktionskörper der Stub-Datei nur die wörtliche Ellipse (...) ist.

Die Typprüfung muss in der Lage sein, den Suchpfad für die Stub-Datei festzulegen. Wenn die Stub-Datei gefunden wird, sollte die Typprüfung das entsprechende "echte" Modul nicht laden.

Obwohl die Stub-Datei ein syntaktisch gültiges Python-Modul ist, verwendet sie die Erweiterung .pyi, damit die Stub-Datei im selben Verzeichnis wie das entsprechende echte Modul verwaltet werden kann. Eine separate Stub-Datei verbessert den Eindruck des erwarteten Verhaltens einer Stub-Datei, die zur Laufzeit nichts bewirkt.

Weitere Hinweise zu Stub-Dateien.

Funktionsüberladung

Mit dem Dekorator "@ overload" können Sie Funktionen schreiben, die verschiedene Kombinationen von Argumenttypen unterstützen. Dieses Muster wird häufig für integrierte Module und integrierte Typen verwendet. Zum Beispiel würde die Methode __getitem__ () vom Typ Bytes wie folgt geschrieben:

from typing import overload

class bytes:
    ...
    @overload
    def __getitem__(self, i: int) -> int:
        ...
    @overload
    def __getitem__(self, s: slice) -> bytes:
        ...

Dieser Code ist strenger als das, was mit dem direkten Summentyp erreicht wird (der die Beziehung zwischen dem Argument und dem Rückgabetyp nicht darstellen kann).

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 integrierten Funktion "map ()". Die Funktion map () akzeptiert je nach Art des aufrufbaren Objekts unterschiedliche Argumente.

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 auch, dass Sie problemlos Elemente hinzufügen können, 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]]:
    ...

Der Dekorator "@ overload" kann nur mit Stub-Dateien verwendet werden. Sie könnten diese Syntax verwenden, um eine Implementierung mehrerer Dispatches bereitzustellen, aber diese Implementierung würde "sys._getframe ()" erfordern, um die Aufmerksamkeit aller zu erregen. Es ist schwierig, einen effizienteren Mehrfachversandmechanismus zu entwerfen und zu implementieren. Aus diesem Grund wurden frühere Versuche abgebrochen und "functools.singledispatch ()" bevorzugt. (Siehe PEP 443, insbesondere im Abschnitt "Alternative Ansätze".) Entwerfen mehrerer Sendungen, um zukünftige Anforderungen zu erfüllen Sie könnten sich etwas einfallen lassen, aber ich möchte nicht, dass dieses Design durch die Überlastungssyntax eingeschränkt wird, die für Hinweise auf Stub-Dateitypen definiert ist. Die Verwendung des Dekorators "@ overload" oder der direkte Aufruf von "overload ()" führt vorerst zu einem "RuntimeError".

Anstatt den Dekorator "@ overload" zu verwenden, wird normalerweise der Typ "TypeVar" mit Einschränkungen verwendet. Beispielsweise sind die Definitionen von "concat1" und "concat2" in dieser Stub-Datei äquivalent.

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat1(x: AnyStr, y: AnyStr) -> AnyStr:
    ...

@overload
def concat2(x: str, y: str) -> str:
    ...

@overload
def concat2(x: bytes, y: bytes) -> bytes:
    ...

Funktionen wie "map" und "bytes .__ getitem__", die oben erwähnt wurden, können mit Typvariablen nicht genau dargestellt werden. Im Gegensatz zu "@ overload" können Typvariablen jedoch auch außerhalb der Stub-Datei verwendet werden. Da "@ overload" eine spezielle Bedingung ist, die nur für Stubs verwendet werden kann, wird empfohlen, sie nur zu verwenden, wenn die Typvariable nicht ausreicht.

Ein weiterer wichtiger Unterschied zwischen der Verwendung von Typvariablen wie "AnyStr" und "@ overload" besteht darin, dass erstere auch zum Definieren von Typparametereinschränkungen für generische Klassen verwendet werden können. Beispielsweise wird eine Einschränkung für den generischen Klassentypparameter "typing.IO" definiert (nur "IO [str]", "IO [Bytes]" und "IO [Any]" sind gültig).

class IO(Generic[AnyStr]):
    ...

Speicherort und Verteilung von Stub-Dateien

Der einfachste Weg, Stub-Dateien zu speichern und zu verteilen, besteht darin, sie im selben Verzeichnis wie das Python-Modul abzulegen. Dies erleichtert sowohl Programmierern als auch Werkzeugen das Auffinden. Paketverwalter können ihren Paketen jedoch auch keine Typhinweise hinzufügen. Daher werden auch Stubs von Drittanbietern unterstützt, die mit dem Befehl pip von PyPI installiert werden können. In diesem Fall sind drei Punkte zu berücksichtigen: Benennung, Version und Installationspfad.

Dieses PEP enthält keine Empfehlungen für Benennungsschemata für Stub-Dateipakete von Drittanbietern. Die Auffindbarkeit hängt hoffentlich von der Beliebtheit des Pakets ab. Zum Beispiel das Django-Paket.

Stubs von Drittanbietern müssen mit der niedrigsten Version eines kompatiblen Quellpakets 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 Stub-Dateipaket-Betreuer kann Stubs für alle Versionen freigeben. Endbenutzer benötigen jedoch mindestens 1.0, 1.1, 2.0 und 2.2, um die Typprüfung für alle Versionen durchzuführen. Der Benutzer weiß, dass die Version mit dem der Paketversion am nächsten gelegenen _low oder same_stub kompatibel ist. Im vorherigen Beispiel würde der Benutzer die Stub-Version 1.1 für FooPackage 1.3 auswählen.

Wenn der Benutzer das "neueste" des verfügbaren Quellpakets verwendet und der Stub häufig aktualisiert wird, funktioniert er im Allgemeinen gut mit der "neuesten" Stub-Datei. Achtung.

Stub-Pakete von Drittanbietern können jeden Ort für die Stub-Speicherung verwenden. Die Typprüfung sollte PYTHONPATH verwenden, um nach Stub-Dateien zu suchen. Das Standard-Fallback-Verzeichnis, das immer überprüft wird, ist "shared / typehints / python3.5 /" (oder beispielsweise 3.6). Da in jeder Umgebung nur ein Paket für eine bestimmte Python-Version installiert ist, werden in diesem Verzeichnis keine weiteren Versionseinstellungen vorgenommen. Der Autor des Stub-Dateipakets kann den folgenden Code in "setup.py" verwenden.

...
data_files=[
    (
        'shared/typehints/python{}.{}'.format(*sys.version_info[:2]),
        pathlib.Path(SRC_PATH).glob('**/*.pyi'),
    ),
],
...

Typeshed Repository

Es gibt ein freigegebenes Repository namens [typeshed] mit einer Sammlung nützlicher Stubs. Bitte beachten Sie, dass Stubs für bestimmte Pakete ohne die ausdrückliche Zustimmung des Paketinhabers hier nicht aufgenommen werden können. Weitere Richtlinien bezüglich der hier gesammelten Stubs werden nach Erörterung in Python-Dev festgelegt und in der README-Datei des typisierten Repositorys veröffentlicht.

Ausnahme

Wir schlagen keine Syntax vor, die explizit die auftretende Ausnahme angibt. Bisher ist der einzige bekannte Anwendungsfall für diese Funktion die Dokumentation. In diesem Fall wird empfohlen, diese Informationen in die Dokumentzeichenfolge einzufügen.

Typing Modul

Ein einheitlicher Namespace ist erforderlich, um die Verwendung der statischen Typisierung auf ältere Versionen sowie Python 3.5 auszudehnen. Zu diesem Zweck werden wir ein neues Modul namens "Typing" in die Standardbibliothek einführen.

Dieses Modul enthält die grundlegenden Komponenten für Gebäudetypen (z. B. "Beliebig"), Typen, die generische Varianten integrierter Sammlungen darstellen (z. B. "Liste"), und Typen, die die abstrakten Basisklassen generischer Sammlungen darstellen. Definieren Sie (z. B. "Sequenz") und fügen Sie andere nützliche Definitionen hinzu.

Grundlegende Bestandteile:

Generische Version der integrierten Sammlung:

Hinweis: Dict, List, Set und FrozenSet sind hauptsächlich nützlich, um den Rückgabewert zu kommentieren. Der unten definierte abstrakte Sammlungstyp ist für das Argument besser geeignet. Zum Beispiel "Mapping", "Sequence" oder "AbstractSet".

Generische Version der abstrakten Container-Basisklasse (und Nicht-Container):

Es werden mehrere einmalige Typen definiert, um eine einzelne spezielle Methode zu untersuchen (ähnlich wie "Hashable" und "Sized"):

Bequeme Definition:

Im Submodul typing.io verfügbare Typen:

Im Submodul typing.re verfügbare Typen:

Abgelehnte Alternative

Während der Diskussion dieses frühen Entwurfs des PEP wurden verschiedene Gegenargumente vorgebracht und eine Reihe von Alternativen vorgeschlagen. Wir werden sie hier diskutieren und erklären, warum wir sie abgelehnt haben.

Ich habe einige der wichtigsten Gegenargumente aufgegriffen.

Welche Klammer verwenden Sie für generische Typparameter?

Die meisten Menschen sind mit spitzen Klammern (z. B. "List ") vertraut, die generische Typparameter in Sprachen wie C ++, Java, C # und Swift darstellen. Das Problem ist, dass es wirklich schwierig ist, spitze Klammern zu analysieren. Dies gilt insbesondere für naive Parser wie Python. In den meisten Sprachen ist es üblich, mit der Mehrdeutigkeit umzugehen, indem nur spitze Klammern in speziellen syntaktischen Positionen und kein Ausdruck zugelassen werden. (Es verwendet auch eine leistungsstarke Parsing-Technologie, die jeden Teil des Akkords zurückverfolgen kann.)

Aber in Python möchte ich, dass der Typausdruck und die anderen Ausdrücke (syntaktisch) gleich sind. Auf diese Weise können Sie Variablen zuweisen, um beispielsweise Typ-Aliase zu erstellen. Betrachten Sie diesen einfachen Typausdruck.

List<int>

Aus Sicht eines Python-Parsers beginnt dieser Ausdruck mit denselben vier Token (NAME, WENIGER, NAME, GRÖSSER) wie der verkettete Vergleich.

a < b > c  # I.e., (a < b) and (b > c)

Sie können auch ein Beispiel erstellen, das auf beide Arten analysiert werden kann.

a < b > [ c ]

Angenommen, die Sprache hat spitze Klammern, kann dieser Code auf zwei Arten interpretiert werden:

(a<b>)[c]      # I.e., (a<b>).__getitem__(c)
a < b > ([c])  # I.e., (a < b) and (b > [c])

Natürlich können Regeln entwickelt werden, um solche Fälle zu disambiguieren. Für viele Benutzer sind die Regeln jedoch willkürlich und komplex. Dies erfordert auch dramatische Änderungen am CPython-Parser (und allen anderen Parsern für Python). Es sollte beachtet werden, dass Pythons aktueller Parser absichtlich "dumm" ist. Dies bedeutet, dass eine präzise Grammatik für den Benutzer leicht zu erraten ist.

Aus all diesen Gründen wurden eckige Klammern (z. B. "List [int]") für die Syntax von generischen Typparametern ausgewählt (und waren schon immer ein Favorit). Dies kann durch Definieren der Methode getitem () in der Metaklasse implementiert werden und erfordert keine neue Syntax. Diese Option funktioniert mit allen neueren Python-Versionen (beginnend mit Python 2.2). Und Python ist nicht der einzige, der diese Syntax wählt. Scala verwendet auch eckige Klammern für generische Klassen.

Was ist mit den vorhandenen Verwendungen von Anmerkungen?

Eines der Argumente weist darauf hin, dass PEP 3107 die Verwendung beliebiger Ausdrücke in Funktionsanmerkungen ausdrücklich unterstützt. Es wird angenommen, dass der neue Vorschlag nicht mit [PEP 3107] kompatibel ist (https://www.python.org/dev/peps/pep-3107).

Die Antwort darauf ist, dass der aktuelle Vorschlag zunächst keine direkte Inkompatibilität verursacht. Daher funktionieren Programme, die Anmerkungen in Python 3.4 verwenden, in Python 3.5 ohne Nachteile einwandfrei.

Letztendlich erwarten wir, dass Typhinweise die einzige Verwendung von Anmerkungen sind. Dies erfordert jedoch eine zusätzliche Diskussion und Außerbetriebnahme, nachdem das Modul für die frühe Eingabe mit Python 3.5 veröffentlicht wurde. Das aktuelle PEP befindet sich bis zur Veröffentlichung von Python 3.6 im Zwischenstatus (siehe [PEP 411](siehe https://www.python.org/dev/peps/pep-0411)). Der schnellstmögliche Plan besteht darin, in Python 3.6 eine anfängliche Stilllegungsperiode für nicht typisierte Hinweisanmerkungen einzuführen, in 3.7 eine vollständige Verfallsperiode, und in 3.8 nur typisierte Hinweisdeklarationen für Anmerkungszwecke zuzulassen. Sie müssen den Autoren von Paketen, die Anmerkungen verwenden, genügend Zeit geben, um andere Ansätze zu entwickeln. Auch wenn Typhinweise schnell erfolgreich sind.

Ein weiteres mögliches Ergebnis ist, dass der Typhinweis letztendlich zur Standardbedeutung der Anmerkung wird, aber immer die Option zum Deaktivieren des Typhinweises behält. Zu diesem Zweck definiert der aktuelle Vorschlag einen Dekorator namens "@ no_type_check", der die Standardinterpretation von Anmerkungen als Typhinweise in einer Klasse oder Funktion überschreibt. Wir definieren auch einen Meta-Dekorator namens "@ no_type_check_decorator", der den Dekorator (!) Dekoriert. Anmerkungen zu Funktionen oder Klassen, die mit einem mit diesem Meta-Dekorator erstellten Dekorator dekoriert wurden, werden jetzt von der Typprüfung ignoriert.

Es gibt auch einen Kommentar "# type: ignore". Die statische Typprüfung sollte eine Konfigurationsoption unterstützen, die die Typprüfung für das ausgewählte Paket deaktiviert.

Trotz all dieser Optionen gibt es endlose Vorschläge, wie Typhinweise und andere Anmerkungsformate für einzelne Argumente nebeneinander existieren können. Ein Vorschlag war, dass, wenn die Annotation für ein Argument ein Wörterbuchliteral wäre, jeder Schlüssel eine andere Form der Annotation darstellen würde, und wenn dieser Schlüssel "Typ" wäre, würde er für einen Typhinweis verwendet. Das Problem bei dieser Idee und ihren Varianten ist, dass die Notation sehr verrauscht und schwer zu lesen ist. In den meisten Fällen vorhandener Bibliotheken, die Anmerkungen verwenden, ist die Notwendigkeit, Typhinweise mit diesen Bibliotheken zu kombinieren, gering. Daher scheint eine präzise Technik, die Typhinweise selektiv deaktiviert, ausreichend zu sein.

Weiterleitungsreferenzproblem

Wenn der Typhinweis eine Vorwärtsreferenz enthalten muss, ist der aktuelle Vorschlag eindeutig eine Problemumgehung. In Python müssen alle Namen definiert sein, wenn sie verwendet werden. Abgesehen von zirkulären Importen ist dies selten ein Problem: "verwendet" bedeutet hier "zur Laufzeit gefunden". Die meisten "Vorwärts" -Referenzen sind in Ordnung, solange Sie sicherstellen, dass der Name definiert ist, bevor die Funktion aufgerufen wird, die ihn verwendet.

Das Problem mit Typhinweisen besteht darin, dass die Annotation bei der Definition der Funktion ausgewertet wird (über PEP 3107 sowie die Standardargumente). Jeder in der Anmerkung verwendete Name muss zum Zeitpunkt der Definition der Funktion vordefiniert sein. Ein häufiges Szenario ist eine Klassendefinition mit Methoden, für die ein Verweis auf die Klasse selbst erforderlich ist, um diese Klasse mit Anmerkungen zu versehen. (Im Allgemeinen geschieht dies auch in wechselseitigen Klassen.) Dies ist für Containertypen selbstverständlich. Zum Beispiel

class Node:
    """Binary tree node."""

    def __init__(self, left: Node, right: Node):
        self.left = left
        self.right = right

Das funktioniert nicht so, wie ich es vorher geschrieben habe. Dies liegt an Pythons Eigenschaft, dass Klassennamen nur definiert werden, wenn der gesamte Hauptteil der Klasse ausgeführt wird. Unsere Lösung besteht darin, die Verwendung von Zeichenfolgenliteralen in Anmerkungen zuzulassen (was nicht besonders ausgefeilt ist, aber gute Arbeit leistet). In den meisten Fällen müssen Sie dies jedoch nicht verwenden. Die meisten use-Typhinweise setzen voraus, dass sie sich auf integrierte Typen oder Typen beziehen, die in anderen Modulen definiert sind.

Ein Gegenvorschlag versuchte, die Semantik eines Typhinweises so zu ändern, dass er zur Laufzeit nie ausgewertet wurde (schließlich erfolgt die Typprüfung offline, sodass der Typhinweis zur Laufzeit ausgewertet werden muss. Ich frage mich, ob es gibt). Dies steht natürlich im Widerspruch zu Abwärtskompatibilitätsproblemen. Der Python-Interpreter weiß nicht wirklich, ob eine bestimmte Anmerkung ein Typhinweis oder etwas anderes sein kann.

Es kann einen Kompromiss geben, der es dem Import "future" ermöglicht, alle _ Annotationen eines bestimmten Moduls wie folgt in Zeichenfolgenliterale zu konvertieren:

from __future__ import annotations

class ImSet:
    def add(self, a: ImSet) -> List[ImSet]:
        ...

assert ImSet.add.__annotations__ == {'a': 'ImSet', 'return': 'List[ImSet]'}

Eine solche "zukünftige" Importanweisung kann in einem anderen PEP vorgeschlagen werden.

Doppelter Doppelpunkt

Mehrere Personen waren kreativ und aufgefordert, eine Lösung für dieses Problem zu finden. Beispielsweise wurde vorgeschlagen, einen Doppelpunkt (::) für Typhinweise zu verwenden, um zwei Probleme gleichzeitig zu lösen: Unterscheidung zwischen anderen Annotationen und Typhinweisen und zur Laufzeit Es ist die Bedeutung zu ändern, damit keine Bewertung erfolgt. Es gab jedoch einige Dinge, die für diese Idee unangemessen waren.

Andere Formen neuer Syntax

Es wurden mehrere andere alternative Syntaxen vorgeschlagen. Zum Beispiel die Einführung des reservierten Wortes "where" [roberge] und die von Cobra inspirierte "Requirements" -Klausel. Es hat jedoch das gleiche Problem wie ein Doppelpunkt, der in früheren Versionen von Python 3 nicht funktioniert. Gleiches gilt für neue "zukünftige" Importe.

Andere Abwärtskompatibilitätsregeln

Enthält Ideen unter Vorschlag.

Oder es wird empfohlen, einfach auf die nächste Version zu warten. Aber welches Problem löst es? Es ist nur eine Verschiebung.

PEP-Entwicklungsprozess

Ein Rohentwurf dieses PEP ist auf [github] zu finden. Es gibt auch einen Issue-Tracker (https://www.python.org/dev/peps/pep-0484/#issues), in dem viele technische Diskussionen stattfinden.

Die Entwürfe auf GitHub werden regelmäßig aktualisiert. Das offizielle PEPS-Repository [peps] wird (normalerweise) nur aktualisiert, wenn ein neuer Entwurf auf python-dev veröffentlicht wird.

Danke

Dieses Dokument wäre ohne das wertvolle Feedback, die Ermutigung und den Rat von Jim Baker, Jeremy Siek, Michael Matson Vitousek, Andrey Vlasovskikh, Radomir Dopieralski, Peter Ludemann und BDFL-Kommissar Mark Shannon nicht fertiggestellt worden.

Es wird von den in PEP 482 genannten Bibliotheken, Frameworks und vorhandenen Sprachen beeinflusst. Dank an die Autoren 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

Referenzen

- -
[mypy] http://mypy-lang.org
[gvr-artima] (1,2) http://www.artima.com/weblogs/viewpost.jsp?thread=85551
[wiki-variance] http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
[typeshed] https://github.com/JukkaL/typeshed/
[pyflakes] https://github.com/pyflakes/pyflakes/
[pylint] http://www.pylint.org
[roberge] http://aroberge.blogspot.com/2015/01/type-hinting-in-python-focus-on.html
[github] https://github.com/ambv/typehinting
[issues] https://github.com/ambv/typehinting/issues
[peps] https://hg.python.org/peps/file/tip/pep-0484.txt

Copyright

Dieses Dokument ist gemeinfrei.

Quelle: https://hg.python.org/peps/file/tip/pep-0484.txt

Recommended Posts

[Übersetzung] PEP 0484 --Tipps
Versuchen Sie es mit Hinweisen
[Übersetzung] Python statischer Typ, erstaunlicher Mypy!
Trainieren! !! Einführung in Python Type (Type Hints)
Ich habe PEP 613 (Explicit Type Aliases) gelesen.
Erhöhen Sie die Sichtbarkeit der Quelle mit Typhinweisen
Japanische Übersetzung: PEP 20 - Das Zen von Python