Neue Typanmerkungen in Python 3.8 (Protokoll, Final, TypedDict, Literal)

Python 3.8 kam heute Abend heraus und Typanmerkungen erhielten neue Funktionen:


  • Protokolle
  • Typisierte Wörterbücher
  • Endspezifizierer
  • Festwertübereinstimmung

Wenn Sie mit Typanmerkungen noch nicht vertraut sind, empfehle ich, auf meine vorherigen Artikel zu achten ( Anfang , Fortsetzung ).
Und während sich alle Sorgen um Walrosse machen, möchte ich kurz auf das Neueste im Schreibmodul eingehen


Protokolle


Python verwendet die Enten-Typisierung und Klassen müssen nicht wie in einigen anderen Sprachen von einer bestimmten Schnittstelle erben.
Leider konnten wir vor Version 3.8 die erforderlichen Anforderungen für ein Objekt nicht mithilfe von Typanmerkungen ausdrücken.
PEP 544 wurde entwickelt, um dieses Problem zu lösen.


Begriffe wie "Iteratorprotokoll" oder "Deskriptorprotokoll" sind bekannt und werden seit langem verwendet.
Jetzt können Sie die Protokolle in Form von Code beschreiben und ihre Konformität in der Phase der statischen Analyse überprüfen.


Es ist erwähnenswert, dass das Typisierungsmodul ab Python 3.6 bereits mehrere Standardprotokolle enthält.
Zum Beispiel SupportsInt (für das die Methode __int__ erforderlich ist), SupportsBytes (für das __bytes__ erforderlich __bytes__ ) und einige andere.


Protokollbeschreibung


Ein Protokoll wird als reguläre Klasse beschrieben, die vom Protokoll erbt. Es kann Methoden (einschließlich solcher mit Implementierung) und Felder enthalten.
Echte Klassen, die das Protokoll implementieren, können davon erben, dies ist jedoch nicht erforderlich.


 from abc import abstractmethod from typing import Protocol, Iterable class SupportsRoar(Protocol): @abstractmethod def roar(self) -> None: raise NotImplementedError class Lion(SupportsRoar): def roar(self) -> None: print("roar") class Tiger: def roar(self) -> None: print("roar") class Cat: def meow(self) -> None: print("meow") def roar_all(bigcats: Iterable[SupportsRoar]) -> None: for t in bigcats: t.roar() roar_all([Lion(), Tiger()]) # ok roar_all([Cat()]) # error: List item 0 has incompatible type "Cat"; expected "SupportsRoar" 

Wir können Protokolle mithilfe der Vererbung kombinieren und neue erstellen.
In diesem Fall müssen Sie jedoch auch das Protokoll explizit als übergeordnete Klasse angeben.


 class BigCatProtocol(SupportsRoar, Protocol): def purr(self) -> None: print("purr") 

Generika, selbst getippt, aufrufbar


Protokolle können wie reguläre Klassen Generika sein. Anstatt Protocol und Generic[T, S,...] als übergeordnete Elemente anzugeben Generic[T, S,...] Sie einfach Protocol[T, S,...] angeben.


Ein weiterer wichtiger Protokolltyp ist der Selbsttyp (siehe PEP 484 ). Zum Beispiel


 C = TypeVar('C', bound='Copyable') class Copyable(Protocol): def copy(self: C) -> C: class One: def copy(self) -> 'One': ... 

Darüber hinaus können Protokolle verwendet werden, wenn die Syntax für Callable Anmerkungen nicht ausreicht.
Beschreiben Sie einfach das Protokoll mit der __call__ Methode der gewünschten Signatur


Laufzeitprüfungen


Obwohl die Protokolle hauptsächlich für die Verwendung durch statische Analysegeräte ausgelegt sind, muss manchmal überprüft werden, ob die Klasse zum gewünschten Protokoll gehört.
Um dies zu ermöglichen, wenden Sie den @runtime_checkable Dekorator auf das Protokoll an, und die isinstance / issubclass Überprüfungen beginnen, die Übereinstimmung mit dem Protokoll zu überprüfen


Diese Funktion unterliegt jedoch einer Reihe von Nutzungsbeschränkungen. Insbesondere werden Generika nicht unterstützt


Typisierte Wörterbücher


Klassen (insbesondere Datenklassen ) oder benannte Tupel werden normalerweise verwendet, um strukturierte Daten darzustellen.
Aber manchmal, zum Beispiel im Fall einer JSON-Strukturbeschreibung, kann es nützlich sein, ein Wörterbuch mit bestimmten Schlüsseln zu haben.
PEP 589 führt das Konzept von TypedDict , das zuvor in Erweiterungen von mypy verfügbar war


Wie bei Datenklassen oder typisierten Tupeln gibt es zwei Möglichkeiten, ein typisiertes Wörterbuch zu deklarieren. Durch Vererbung oder Verwendung einer Fabrik:


 class Book(TypedDict): title: str author: str AlsoBook = TypedDict("AlsoBook", {"title": str, "author": str}) # same as Book book: Book = {"title": "Fareneheit 481", "author": "Bradbury"} # ok other_book: Book = {"title": "Highway to Hell", "artist": "AC/DC"} # error: Extra key 'artist' for TypedDict "Book" another_book: Book = {"title": "Fareneheit 481"} # error: Key 'author' missing for TypedDict "Book" 

Typisierte Wörterbücher unterstützen die Vererbung:


 class BookWithDesc(Book): desc: str 

Standardmäßig sind alle Wörterbuchschlüssel erforderlich. Sie können dies jedoch deaktivieren, indem Sie beim Erstellen der Klasse total=False .
Dies gilt nur für Schlüssel, die in der aktuellen Abendkasse beschrieben sind, und hat keine Auswirkungen auf geerbte Schlüssel


 class SimpleBook(TypedDict, total=False): title: str author: str simple_book: SimpleBook = {"title": "Fareneheit 481"} # ok 

Die Verwendung von TypedDict einer Reihe von Einschränkungen. Insbesondere:


  • Überprüfungen der Laufzeit über isinstance werden nicht unterstützt
  • Schlüssel müssen Literale oder Endwerte sein

Darüber hinaus sind unsichere Operationen wie .clear oder del mit einem solchen Wörterbuch verboten.
Die Arbeit an einem Schlüssel, der kein Literal ist, kann ebenfalls verboten werden, da in diesem Fall die erwartete Art des Werts nicht bestimmt werden kann


Letzter Modifikator


PEP 591 führt den endgültigen Modifikator (als Dekorator und Anmerkung) für verschiedene Zwecke ein


  • Bezeichnung einer Klasse, von der es unmöglich ist zu erben:

 from typing import final @final class Childfree: ... class Baby(Childfree): # error: Cannot inherit from final class "Childfree" ... 

  • Die Bezeichnung der Methode, deren Überschreiben verboten ist:

 from typing import final class Base: @final def foo(self) -> None: ... class Derived(Base): def foo(self) -> None: # error: Cannot override final attribute "foo" (previously declared in base class "Base") ... 

  • Bezeichnung einer Variablen (Funktionsparameter. Klassenfeld), deren Neuzuweisung verboten ist.

 ID: Final[float] = 1 ID = 2 # error: Cannot assign to final name "ID" SOME_STR: Final = "Hello" SOME_STR = "oops" # error: Cannot assign to final name "SOME_STR" letters: Final = ['a', 'b'] letters.append('c') # ok class ImmutablePoint: x: Final[int] y: Final[int] # error: Final name must be initialized with a value def __init__(self) -> None: self.x = 1 # ok ImmutablePoint().x = 2 # error: Cannot assign to final attribute "x" 

In diesem Fall ist ein Code der Form self.id: Final = 123 zulässig, jedoch nur in der Methode __init__


Wörtlich


Literal in PEP 586 definierte Typ wird verwendet, wenn Sie bestimmte Werte buchstäblich überprüfen müssen


Zum Beispiel bedeutet Literal[42] , dass nur 42 als Wert erwartet wird.
Es ist wichtig, dass nicht nur die Gleichheit des Werts überprüft wird, sondern auch sein Typ (zum Beispiel kann False nicht verwendet werden, wenn 0 erwartet wird).


 def give_me_five(x: Literal[5]) -> None: pass give_me_five(5) # ok give_me_five(5.0) # error: Argument 1 to "give_me_five" has incompatible type "float"; expected "Literal[5]" give_me_five(42) # error: Argument 1 to "give_me_five" has incompatible type "Literal[42]"; expected "Literal[5]" 

In diesem Fall können mehrere Werte in Klammern übertragen werden, was der Verwendung von Union entspricht (die Wertetypen stimmen möglicherweise nicht überein).


Ausdrücke (z. B. Literal[1+2] ) oder Werte veränderlicher Typen können nicht als Werte verwendet werden.


Als ein nützliches Beispiel ist die Verwendung von Literal die Funktion open() , die bestimmte mode erwartet.


Typbehandlung zur Laufzeit


Wenn Sie verschiedene Arten von Informationen (wie ich ) verarbeiten möchten, während das Programm ausgeführt wird,
Die Funktionen get_origin und get_args sind jetzt verfügbar.


Für einen Typ der Form X[Y, Z,...] Typ X als Ursprung und (Y, Z, ...) als Argumente zurückgegeben
Es ist zu beachten, dass X, wenn es sich um einen Alias ​​für den integrierten Typ oder den Typ aus dem collections , durch das Original ersetzt wird.


 assert get_origin(Dict[str, int]) is dict assert get_args(Dict[int, str]) == (int, str) assert get_origin(Union[int, str]) is Union assert get_args(Union[int, str]) == (int, str) 

Leider hat die Funktion für __parameters__ nicht funktioniert


Referenzen


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


All Articles