Eine Einführung in Anmerkungen vom Typ Python. Fortsetzung


Illustration von Magdalena Tomczyk


Im ersten Teil des Artikels habe ich die Grundlagen der Verwendung von Typanmerkungen beschrieben. Einige wichtige Punkte wurden jedoch nicht berücksichtigt. Erstens sind Generika ein wichtiger Mechanismus, und zweitens kann es manchmal nützlich sein, Informationen über die erwarteten Typen zur Laufzeit zu erhalten. Aber ich wollte mit einfacheren Dingen beginnen


Vorankündigung


Normalerweise können Sie einen Typ nicht verwenden, bevor er erstellt wurde. Beispielsweise wird der folgende Code nicht einmal gestartet:


class LinkedList: data: Any next: LinkedList # NameError: name 'LinkedList' is not defined 

Um dies zu beheben, kann ein String-Literal verwendet werden. In diesem Fall werden Anmerkungen aufgeschoben berechnet.


 class LinkedList: data: Any next: 'LinkedList' 

Sie können auch von anderen Modulen aus auf Klassen zugreifen (natürlich, wenn das Modul importiert wird): some_variable: 'somemodule.SomeClass'


Bemerkung

Im Allgemeinen kann jeder berechenbare Ausdruck als Anmerkung verwendet werden. Es wird jedoch empfohlen, sie so einfach wie möglich zu halten, damit die Dienstprogramme für statische Analysen sie verwenden können. Insbesondere werden sie dynamisch berechenbare Typen höchstwahrscheinlich nicht verstehen. Weitere Informationen zu Einschränkungen finden Sie hier: PEP 484 - Typhinweise # Akzeptable Typhinweise


Der folgende Code funktioniert beispielsweise, und zur Laufzeit sind sogar Anmerkungen verfügbar. Mypy gibt jedoch einen Fehler aus


 def get_next_type(arg=None): if arg: return LinkedList else: return Any class LinkedList: data: Any next: 'get_next_type()' # error: invalid type comment or annotation 

UPD : In Python 4.0 ist die Berechnung von verzögerten Typanmerkungen ( PEP 563 ) geplant, mit der diese Technik mit Zeichenfolgenliteralen beseitigt wird. Mit Python 3.7 können Sie neues Verhalten mithilfe des from __future__ import annotations


Funktionen und aufgerufene Objekte


In Situationen, in denen eine Funktion übertragen werden muss oder ein anderes Objekt aufgerufen wird (z. B. als Rückruf), müssen Sie die Annotation Callable [[ArgType1, ArgType2, ...], ReturnType] verwenden.
Zum Beispiel


 def help() -> None: print("This is help string") def render_hundreds(num: int) -> str: return str(num // 100) def app(helper: Callable[[], None], renderer: Callable[[int], str]): helper() num = 12345 print(renderer(num)) app(help, render_hundreds) app(help, help) # error: Argument 2 to "app" has incompatible type "Callable[[], None]"; expected "Callable[[int], str]" 

Es ist gültig, nur den Rückgabetyp der Funktion anzugeben, ohne deren Parameter anzugeben. In diesem Fall wird die Ellipse verwendet: Callable[..., ReturnType] . Beachten Sie, dass die Auslassungspunkte keine eckigen Klammern enthalten.


Derzeit ist es unmöglich, eine Funktionssignatur mit einer variablen Anzahl von Parametern eines bestimmten Typs zu beschreiben oder benannte Argumente anzugeben.


Generische Typen


Manchmal ist es notwendig, Informationen über einen Typ zu speichern, ohne sie starr zu fixieren. Zum Beispiel, wenn Sie einen Container schreiben, in dem dieselben Daten gespeichert sind. Oder eine Funktion, die Daten des gleichen Typs wie eines der Argumente zurückgibt.


Typen wie List oder Callable, die wir zuvor gesehen haben, verwenden nur den generischen Mechanismus. Neben Standardtypen können Sie jedoch auch eigene generische Typen erstellen. Dazu erhalten Sie zum einen eine TypeVar-Variable, die ein Attribut des generischen Typs ist, und zum anderen deklarieren Sie direkt einen generischen Typ:


 T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" def __init__(self, data: T): self.data = data head_int: LinkedList[int] = LinkedList(1) head_int.next = LinkedList(2) head_int.next = 2 # error: Incompatible types in assignment (expression has type "int", variable has type "LinkedList[int]") head_int.data += 1 head_int.data.replace("0", "1") # error: "int" has no attribute "replace" head_str: LinkedList[str] = LinkedList("1") head_str.data.replace("0", "1") head_str = LinkedList[str](1) # error: Argument 1 to "LinkedList" has incompatible type "int"; expected "str" 

Wie Sie sehen können, funktioniert bei generischen Typen die automatische Inferenz des Parametertyps.


Bei Bedarf kann ein Generikum eine beliebige Anzahl von Parametern haben: Generic[T1, T2, T3] .


Außerdem können Sie beim Definieren von TypeVar gültige Typen einschränken:


 T2 = TypeVar("T2", int, float) class SomethingNumeric(Generic[T2]): pass x = SomethingNumeric[str]() # error: Value of type variable "T2" of "SomethingNumeric" cannot be "str" 

Besetzung


Manchmal kann der statische Analysator des Analysators den Variablentyp nicht korrekt bestimmen. In diesem Fall können Sie die Cast-Funktion verwenden. Die einzige Aufgabe besteht darin, dem Analysator zu zeigen, dass der Ausdruck von einem bestimmten Typ ist. 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)) return cast(str, a[index]) 

Es kann auch für Dekorateure nützlich sein:


 MyCallable = TypeVar("MyCallable", bound=Callable) def logged(func: MyCallable) -> MyCallable: @wraps(func) def wrapper(*args, **kwargs): print(func.__name__, args, kwargs) return func(*args, **kwargs) return cast(MyCallable, wrapper) @logged def mysum(a: int, b: int) -> int: return a + b mysum(a=1) # error: Missing positional argument "b" in call to "mysum" 

Arbeiten Sie mit Laufzeitanmerkungen


Obwohl der Interpreter keine eigenen Anmerkungen verwendet, sind diese für Ihren Code verfügbar, während das Programm ausgeführt wird. Zu diesem __annotations__ wird ein __annotations__ , das ein Wörterbuch mit den angegebenen Annotationen enthält. Bei Funktionen sind dies Parameteranmerkungen und Rückgabetyp, für ein Objekt Feldanmerkungen, für den globalen Bereich, Variablen und deren Anmerkungen.


 def render_int(num: int) -> str: return str(num) print(render_int.annotations) # {'num': <class 'int'>, 'return': <class 'str'>} 

get_type_hints ist ebenfalls verfügbar. Es gibt Anmerkungen für das übergebene Objekt zurück. In vielen Situationen entspricht es dem Inhalt von __annotations__ . Es gibt jedoch Unterschiede: Es werden auch Anmerkungen zu den übergeordneten Objekten __mro__ (in umgekehrter Reihenfolge von __mro__ ) und vorläufige Deklarationen von Typen, die als Zeichenfolgen angegeben werden.


 T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" print(LinkedList.__annotations__) # {'data': ~T, 'next': 'LinkedList[T]'} print(get_type_hints(LinkedList)) # {'data': ~T, 'next': __main__.LinkedList[~T]} 

Für generische Typen sind Informationen über den Typ selbst und seine Parameter über die __args__ __origin__ und __args__ verfügbar. Dies ist jedoch nicht Teil des Standards, und das Verhalten hat sich bereits zwischen den Versionen 3.6 und 3.7 geändert

Source: https://habr.com/ru/post/de435988/


All Articles