In Python 3.5 wurde das neue Typisierungsmodul eingeführt, das standardmäßige Bibliotheksunterstützung für die Verwendung von Funktionsanmerkungen für optionale Typhinweise bietet. Dies öffnet die Tür für neue und interessante Tools für die statische Typprüfung wie mypy und in Zukunft möglicherweise automatisierte typbasierte Optimierung. Typenhinweise werden in PEP-483 und PEP-484 angegeben.
In diesem Lernprogramm untersuche ich die Möglichkeiten, die mit den Typ-Hinweisen verbunden sind, und zeige Ihnen, wie Sie mypy verwenden, um Ihre Python-Programme statisch zu analysieren und die Qualität Ihres Codes erheblich zu verbessern.
Typhinweise basieren auf Funktionsanmerkungen. Mit Funktionsanmerkungen können Sie die Argumente und den Rückgabewert einer Funktion oder Methode mit beliebigen Metadaten kurz kommentieren. Typhinweise sind ein spezieller Fall von Funktionsanmerkungen, die Funktionsargumente und den Rückgabewert speziell mit Standardtypinformationen versehen. Funktionsanmerkungen im Allgemeinen und Typhinweise im Besonderen sind vollständig optional. Schauen wir uns ein kurzes Beispiel an:
"python def reverse_slice (text: str, start: int, end: int) -> str: return text [start: end] [:: - 1]
reverse_slice ('abcdef', 3, 5) 'ed "
Die Argumente wurden mit ihrem Typ sowie mit dem Rückgabewert versehen. Es ist jedoch wichtig zu wissen, dass Python dies vollständig ignoriert. Es macht die Typinformationen über die Anmerkungen Attribut des Funktionsobjekts, aber das war es auch schon.
python reverse_slice .__ Anmerkungen 'end': int, 'return': str, 'start': int, 'text': str
Um zu überprüfen, ob Python die Typhinweise wirklich ignoriert, lassen Sie uns die Typhinweise völlig durcheinander bringen:
"python def reverse_slice (text: float, start: str, end: bool) -> dict: return text [start: end] [:: - 1]
reverse_slice ('abcdef', 3, 5) 'ed "
Wie Sie sehen, verhält sich der Code unabhängig von den Typhinweisen gleich.
OK. Typenhinweise sind optional. Typhinweise werden von Python vollständig ignoriert. Was ist der Sinn von ihnen dann? Nun, es gibt mehrere gute Gründe:
Ich werde später mit Mypy in die statische Analyse eintauchen. Die IDE-Unterstützung hat bereits mit der Unterstützung von PyCharm 5 für Typhinweise begonnen. Die Standarddokumentation eignet sich hervorragend für Entwickler, die leicht den Typ der Argumente und den Rückgabewert herausfinden können, indem sie eine Funktionssignatur sowie automatische Dokumentationsgeneratoren betrachten, die die Typinformationen aus den Hinweisen extrahieren können.
tippen
ModulDas Typisierungsmodul enthält Typen, die zur Unterstützung von Typhinweisen entwickelt wurden. Warum verwenden Sie nicht einfach vorhandene Python-Typen wie int, str, list und dict? Sie können diese Typen definitiv verwenden, aber aufgrund der dynamischen Typisierung von Python erhalten Sie neben den Basistypen nicht viele Informationen. Wenn Sie beispielsweise angeben möchten, dass ein Argument eine Zuordnung zwischen einer Zeichenfolge und einer Ganzzahl sein kann, gibt es keine Möglichkeit, dies mit Standard-Python-Typen zu tun. Mit dem Schreibmodul ist es so einfach wie:
Python-Mapping [str, int]
Schauen wir uns ein vollständigeres Beispiel an: eine Funktion, die zwei Argumente benötigt. Eines davon ist eine Liste von Wörterbüchern, in denen jedes Wörterbuch Schlüssel enthält, die Zeichenfolgen und Werte sind, die ganze Zahlen sind. Das andere Argument ist entweder eine Zeichenfolge oder eine Ganzzahl. Das Typisierungsmodul ermöglicht die genaue Angabe solcher komplizierten Argumente.
"Python von der Eingabe von Import List, Dict, Union
def foo (a: Liste [dict [str, int]], b: Union [str, int]) -> int: "" Drucken einer Liste von Wörterbüchern und Rückgabe der Anzahl der Wörterbücher "", wenn str): b = int (b) für i im Bereich (b): print (a)
x = [dikt (a = 1, b = 2), dikt (c = 3, d = 4)] foo (x, '3')
['b': 2, 'a': 1, 'd': 4, 'c': 3] ['b': 2, 'a': 1, 'd': 4 , 'c': 3] ['b': 2, 'a': 1, 'd': 4, 'c': 3] "
Sehen wir uns einige der interessanteren Typen aus dem Typisierungsmodul an.
Mit dem Typ Callable können Sie die Funktion angeben, die als Argumente übergeben oder als Ergebnis zurückgegeben werden kann, da Python Funktionen als erstklassige Bürger behandelt. Die Syntax für Callables besteht darin, ein Array von Argumenttypen (wiederum vom Typisierungsmodul) bereitzustellen, gefolgt von einem Rückgabewert. Wenn das verwirrend ist, hier ein Beispiel:
"python def do_something_fancy (data: Set [float], on_error: Callable [[Exception, int], None]):…
"
Die Callback-Funktion on_error wird als Funktion angegeben, die eine Exception und eine Ganzzahl als Argumente akzeptiert und nichts zurückgibt.
Der Typ Any bedeutet, dass ein statischer Typprüfer jede Operation sowie die Zuordnung zu einem anderen Typ zulassen soll. Jeder Typ ist ein Untertyp von Any.
Der Union-Typ, den Sie zuvor gesehen haben, ist nützlich, wenn ein Argument mehrere Typen haben kann, was in Python sehr häufig ist. Im folgenden Beispiel wird der verify_config () Die Funktion akzeptiert ein Konfigurationsargument, das entweder ein Konfigurationsobjekt oder ein Dateiname sein kann. Wenn es sich um einen Dateinamen handelt, wird eine andere Funktion aufgerufen, um die Datei in ein Config-Objekt zu parsen und zurückzugeben.
"python def verify_config (config: Union [str, Config]): wenn isinstance (config, str): config = parse_config_file (config)…
def parse_config_file (Dateiname: str) -> Config:…
"
Der optionale Typ bedeutet, dass das Argument auch Keine sein darf. Optional [T]
ist äquivalent zu Union [T, keine]
Es gibt viele weitere Typen, die verschiedene Fähigkeiten anzeigen, wie Iterable, Iterator, Reversible, SupportsInt, SupportsFloat, Sequence, MutableSequence und IO. Die vollständige Liste finden Sie in der Dokumentation zum Typisierungsmodul.
Die Hauptsache ist, dass Sie den Typ der Argumente auf eine sehr feinkörnige Weise angeben können, die das Python-Typsystem mit hoher Wiedergabetreue unterstützt und auch Generika und abstrakte Basisklassen zulässt.
Manchmal möchten Sie auf eine Klasse in einem Typhinweis innerhalb einer ihrer Methoden verweisen. Nehmen wir beispielsweise an, dass Klasse A eine Zusammenführungsoperation ausführen kann, die eine andere Instanz von A benötigt, mit sich selbst zusammenführt und das Ergebnis zurückgibt. Hier ist ein naiver Versuch, Typhinweise zu verwenden, um ihn anzugeben:
"Python-Klasse A: def merge (andere: A) -> A:…
1 Klasse A: ----> 2 def merge (andere: A = Keine) -> A: 3… 4
NameError: Name 'A' ist nicht definiert "
Was ist passiert? Die Klasse A ist noch nicht definiert, wenn der Typhinweis für die Methode merge () von Python geprüft wird. Daher kann die Klasse A an dieser Stelle (direkt) nicht verwendet werden. Die Lösung ist recht einfach und ich habe sie schon von SQLAlchemy verwendet. Sie geben nur den Typhinweis als Zeichenfolge an. Python wird verstehen, dass es sich um eine Vorwärtsreferenz handelt und das Richtige tun wird:
Python-Klasse A: def merge (andere: 'A' = Keine) -> 'A':…
Ein Nachteil der Verwendung von Typhinweisen für lange Typspezifikationen besteht darin, dass der Code durcheinander geraten und weniger lesbar werden kann, selbst wenn er viele Typinformationen enthält. Sie können Alias-Typen wie jedes andere Objekt verwenden. Es ist so einfach wie:
"python Data = Dict [int, Sequenz [Dict [str, optional [Liste [float]]]]
def foo (Daten: Daten) -> Bool:… "
get_type_hints ()
HelferfunktionDas Typisierungsmodul stellt die Funktion get_type_hints () bereit, die Informationen über die Argumenttypen und den Rückgabewert liefert. Während Anmerkungen Attribut gibt Typhinweise zurück, da es sich lediglich um Anmerkungen handelt. Ich empfehle dennoch, die Funktion get_type_hints () zu verwenden, da Vorwärtsreferenzen aufgelöst werden. Wenn Sie für eines der Argumente den Standardwert None angeben, gibt die Funktion get_type_hints () ihren Typ automatisch als Union [T, NoneType] zurück, wenn Sie gerade T angegeben haben. Der Unterschied wird mit der A.merge () -Methode erkannt früher definiert:
"Python - Druck (A.merge.Anmerkungen)
'other': 'A', 'return': 'A' "
Das Anmerkungen Das Attribut gibt einfach den Anmerkungswert zurück. In diesem Fall ist es nur die Zeichenfolge 'A' und nicht das A-Klassenobjekt, auf das 'A' nur eine Vorwärtsreferenz ist.
"python print (get_type_hints (A.merge))
'Rückkehr':
Die Funktion get_type_hints () konvertierte den Typ von andere Argument für eine Union von A (die Klasse) und NoneType wegen des Standardarguments None. Der Rückgabetyp wurde ebenfalls in die Klasse A konvertiert.
Typhinweise sind eine Spezialisierung von Funktionsanmerkungen und können auch neben anderen Funktionsanmerkungen arbeiten.
Dafür bietet das Typisierungsmodul zwei Dekoratoren: @no_type_check und @no_type_check_decorator. Das @no_type_check Der Dekorateur kann entweder auf eine Klasse oder eine Funktion angewendet werden. Es fügt das hinzu no_type_check Attribut für die Funktion (oder jede Methode der Klasse). Auf diese Weise wissen Typprüfer, Anmerkungen zu ignorieren, bei denen es sich nicht um Typhinweise handelt.
Dies ist ein wenig umständlich, denn wenn Sie eine Bibliothek schreiben, die allgemein verwendet wird, müssen Sie davon ausgehen, dass ein Typprüfer verwendet wird. Wenn Sie Ihre Funktionen mit nicht-typischen Hinweisen versehen möchten, müssen Sie sie auch mit dekorieren @no_type_check.
Ein übliches Szenario bei der Verwendung von regulären Funktionsanmerkungen besteht auch darin, einen Dekorator zu haben, der über sie arbeitet. In diesem Fall möchten Sie auch die Typprüfung deaktivieren. Eine Möglichkeit ist die Verwendung der @no_type_check Dekorateur zusätzlich zu Ihrem Dekorateur, aber das wird alt. Stattdessen die @no_Type_check_decorator kann verwendet werden, um Ihren Dekorateur so zu dekorieren, dass er sich auch so verhält @no_type_check (fügt das hinzu no_type_check Attribut).
Lassen Sie mich all diese Konzepte veranschaulichen. Wenn Sie versuchen, get_type_hint () (wie jeder Typenprüfer dies tun wird) für eine Funktion, die mit einer regulären String-Annotation versehen ist, zu interpretieren, wird dies von get_type_hints () als Vorwärtsreferenz interpretiert:
"python def f (a: 'some annotation'): pass
print (get_type_hints (f))
SyntaxError: ForwardRef muss ein Ausdruck sein - hat einige Anmerkungen erhalten
Um dies zu vermeiden, fügen Sie den @ no_type_check-Dekorator hinzu, und get_type_hints gibt einfach ein leeres Diktat zurück, während das __annotations__ Attribut gibt die Anmerkungen zurück:
"python @no_type_check def f (a: 'some annotation'): pass
print (get_type_hints (f))
drucken (f.Anmerkungen) 'a': 'einige Anmerkungen' "
Nehmen wir an, wir haben einen Dekorateur, der das Anmerkungsdiktat druckt. Sie können es mit schmücken @no_Type_check_decorator und dann dekorieren Sie die Funktion und machen Sie sich keine Sorgen darüber, dass ein Typprüfer get_type_hints () aufruft und verwirrt wird. Dies ist wahrscheinlich eine bewährte Methode für jeden Dekorateur, der mit Anmerkungen arbeitet. Vergiss das nicht @ functools.wraps, Andernfalls werden die Anmerkungen nicht in die dekorierte Funktion kopiert und alles fällt auseinander. Dies wird ausführlich in Python 3 Function Annotations behandelt.
python @no_type_check_decorator def print_annotations (f): @ functools.wraps (f) def decor (* args, ** kwargs): print (f .__ annotations__) gibt f (* args, ** kwargs) und dekoriert zurück
Jetzt können Sie die Funktion einfach mit dekorieren @print_annotations, und wann immer es aufgerufen wird, werden seine Anmerkungen gedruckt.
"python @print_annotations def f (a: 'some annotation'): pass
f (4) 'a': 'einige Anmerkungen' "
Berufung get_type_hints () ist auch sicher und gibt ein leeres Dikt zurück.
python print (get_type_hints (f))
Mypy ist ein statischer Typprüfer, der als Inspiration für Typhinweise und das Typisierungsmodul diente. Guido van Rossum ist selbst Autor von PEP-483 und Mitautor von PEP-484.
Mypy befindet sich in einer sehr aktiven Entwicklung, und zum jetzigen Zeitpunkt ist das Paket auf PyPI nicht mehr aktuell und funktioniert nicht mit Python 3.5. Um Mypy mit Python 3.5 zu verwenden, erhalten Sie die neuesten Informationen aus Mypys Repository auf GitHub. Es ist so einfach wie:
bash pip3 install git + git: //github.com/JukkaL/mypy.git
Sobald Sie Mypy installiert haben, können Sie Mypy einfach in Ihren Programmen ausführen. Das folgende Programm definiert eine Funktion, die eine Liste von Strings erwartet. Dann wird die Funktion mit einer Liste von Ganzzahlen aufgerufen.
"Python von der Eingabe der Importliste
def case_insensitive_dedupe (data: List [str]): "" "Konvertiert alle Werte in Kleinbuchstaben und entfernt Duplikate" "" - Rückgabeliste (set (x.lower () für x in data))
print (case_insensitive_dedupe ([1, 2])) "
Beim Ausführen des Programms schlägt es offensichtlich zur Laufzeit mit dem folgenden Fehler fehl:
plain python3 dedupe.py Traceback (letzter Aufruf zuletzt): Datei "dedupe.py", Zeile 8, in
Was ist das Problem damit? Das Problem ist, dass selbst in diesem sehr einfachen Fall nicht sofort klar ist, was die Ursache ist. Ist es ein Eingabeproblem? Oder vielleicht ist der Code selbst falsch und sollte nicht versuchen, das anzurufen niedriger() Methode für das 'int'-Objekt. Ein anderes Problem ist, dass, wenn Sie keine 100% ige Testabdeckung haben (und, um ehrlich zu sein, keiner von uns), diese Probleme in einem ungeprüften, selten genutzten Codepfad lauern können und zur schlechtesten Zeit in der Produktion erkannt werden.
Statische Typisierung mit Hilfe von Typhinweisen gibt Ihnen ein zusätzliches Sicherheitsnetz, indem Sie sicherstellen, dass Sie Ihre Funktionen (mit Typhinweisen versehen) immer mit den richtigen Typen aufrufen. Hier ist die Ausgabe von Mypy:
plain (N)> mypy dedupe.py dedupe.py:8: Fehler: Listenelement 0 hat inkompatiblen Typ "int" dedupe.py:8: Fehler: Listenelement 1 hat inkompatiblen Typ "int" dedupe.py:8: Fehler : Listenelement 2 hat den inkompatiblen Typ "int"
Dies ist unkompliziert, weist direkt auf das Problem hin und erfordert keine großen Tests. Ein weiterer Vorteil der statischen Typprüfung besteht darin, dass Sie beim Festschreiben die dynamische Typprüfung überspringen können, außer beim Analysieren externer Eingaben (Lesen von Dateien, eingehenden Netzwerkanforderungen oder Benutzereingaben). Auch beim Refactoring wird viel Vertrauen aufgebaut.
Typhinweise und das Typisierungsmodul sind optionale Ergänzungen zur Ausdruckskraft von Python. Sie sind zwar nicht jedermanns Geschmack, für große Projekte und große Teams jedoch unverzichtbar. Der Beweis ist, dass große Teams bereits statische Typprüfung verwenden. Da die Typinformationen nun standardisiert sind, wird es einfacher, Code, Dienstprogramme und Tools, die sie verwenden, gemeinsam zu nutzen. IDEs wie PyCharm nutzen es bereits, um ein besseres Entwicklererlebnis zu bieten.