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()])
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})
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"}
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):
- 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:
- Bezeichnung einer Variablen (Funktionsparameter. Klassenfeld), deren Neuzuweisung verboten ist.
ID: Final[float] = 1 ID = 2
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)
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