Dies ist der Artikel am 21. Tag von KLab Adventskalender 2016.
Ich habe Lust auf Mypy, also werde ich eine Geschichte schreiben, die ich mit internem Code ausprobiert habe.
Die Syntax für Typanmerkungen (PEP 484) wurde seit Python3.5 hinzugefügt. Mypy analysiert statisch den Typ im Code basierend auf dieser Anmerkung.
def func() -> int:
return "a"
Wenn ich mypy mit Code wie diesem ausführe
$ mypy test.py
test.py: note: In function "func":
test.py:2: error: Incompatible return value type (got "str", expected "int")
Der Typ wird basierend auf der Annotation überprüft, und str wird zurückgegeben, obwohl der Rückgabetyp int ist, sodass ein Fehler auftritt.
"Go mypy" bedeutet, mypy auszuführen und die statische Prüfung mit der Typanmerkung zu bestehen, die PEP 484 entspricht. Der Grund, warum ich Lust dazu hatte, war [Statische Typen in Python, oh mein (py)!](Http://blog.zulip.org/2016/10/13/static-types-in-python-oh -mypy /) Ich habe diesen Artikel viel gelesen. Ich hatte das Gefühl, ich könnte es schaffen. Da das Projekt, für das ich verantwortlich war, groß war und die vorhandenen Funktionen häufig geändert wurden, dachte ich, dass eine zuverlässige Typanmerkung mir helfen würde, das Problem zu beheben. Ein weiterer Grund ist, dass PyCharm jetzt PEP 484 unterstützt und eine Warnung ausgibt, sodass das Teil mit dem falschen Typhinweis zu einem Fehler wird und auffällt.
Die Codebasis wurde in Python3.5 geschrieben und mit losen Regeln wie "Annotation wird von der Person hinzugefügt, die sie möchte" entwickelt. Etwa 70% der Methoden mit Ausnahme von Chargen und Tests sind mit Anmerkungen versehen. Vorerst
mypy -s --fast-parser --strict-optional --disallow-untyped-defs --disallow-untyped-calls <dir>
Als ich lief, bekam ich fast 3000 Fehler. Da das Zerkleinern in der Reihenfolge von oben schmerzhaft sein kann, habe ich beschlossen, es auf den Teil zu beschränken, in dem die Geschäftslogik verstopft ist, und es nur mit den minimalen Optionen auszuführen, um es zu korrigieren.
Die Version von mypy ist 0.4.6 und Python ist 3.5.2.
mypy -s --fast-parser <dir>
Ich habe es beim Laufen behoben.
Die Option -s
überprüft das importierte Modul nicht, andernfalls wird die importierte Bibliothek eines Drittanbieters überprüft. Der Modulimport in das durch <dir>
angegebene Verzeichnis macht einen guten Job. Der Typ des Arguments und des Rückgabewerts beim Aufrufen der Funktion des externen Moduls ist "Any".
Die Option "--fast-parser" ist die Standardoption, und beim aktuellen Standardparser gibt es ein Muster, das beim Entpacken des Arguments einen Analysefehler verursacht. Nur --fast-parser
unterstützt die Syntax von python3.6.
Es gab so viele Stellen in der Klasse, die nicht die Typanmerkung der Methode __init __ hatten. Erstens war ich mir nicht bewusst, "init" zu kommentieren.
Note that the return type of init ought to be annotated with -> None .
PEP484#the-meaning-of-annotations Setzen wir also den Rückgabetyp auf "Keine". Glücklicherweise kann dieses Muster mechanisch behandelt werden, daher habe ich ein geeignetes Skript geschrieben, um damit umzugehen.
imported but unused
Es gibt ein statisches Analysetool von Python namens flake8, mit dem der CI-Server überprüft wird. Einige Flake8-Testfälle suchen nach nicht verwendeten Importanweisungen, die beim Importieren, aber nie verwendet, zu einem Fehler führen. Das Problem hierbei ist, wenn Sie Variablen anstelle von Funktionen mit Anmerkungen versehen möchten. Wenn in Python3.5 eine Variable mit Anmerkungen versehen wird, soll sie einem Kommentar entsprechen. (python3.6 fügt eine neue Syntax zum Hinzufügen variabler Anmerkungen hinzu. PEP526)
from typing import Optional
a = None # type: Optional[int]
Ich schreibe variable Anmerkungen wie diese, aber da es sich um einen Kommentar handelt, führe ich "flake8" aus
$ flake8 ./test.py
./test.py:1:1: F401 'Optional' imported but unused
Ich bekomme eine Fehlermeldung. Also habe ich "# noqa" und einen Kommentar zum Importteil des Moduls hinzugefügt, in dem dieser Fehler aufgetreten ist, um den Fehler zu unterdrücken.
from typing import Optional # noqa
a = None # type: Optional[int]
Von Python3.6
a: Optional[int] = None
Da es geschrieben werden kann, ist es weniger wahrscheinlich, dass das Problem des nicht verwendeten Imports durch variable Annotation auftritt.
Beim Annotieren von Modulvariablen und -methoden besteht ein Muster des gegenseitigen Imports zwischen Modulen. In diesem Fall tritt zur Laufzeit eine Schleife auf und es tritt ein Fehler auf, sodass Gegenmaßnahmen erforderlich sind. Ab python3.5.2 steht eine Variable namens typing.TYPE_CHECKING
zur Verfügung, die von einem Drittanbieter erstellt wird. Eine Variable, die nur dann "True" ist, wenn das Tool sie liest, um sie zu vermeiden. Diese Variable wird verwendet, um zu vermeiden, dass das Zielmodul nur während der Analyse auf der Importseite geladen wird.
Die Lösung ist auch in der offiziellen mypy-Dokumentation aufgeführt.
http://mypy.readthedocs.io/en/latest/common_issues.html#import-cycles
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from module import MyClass # noqa
def func() -> "MyClass":
return MyClass()
# Typ: ignorieren
Ich konnte den Test von mypy unterdrücken, indem ich den Kommentar "# type: ignore" hinzufügte, aber diese Position war der Songwriter. Wenn in einem mehrzeiligen Ausdruck ein Fehler auftritt, müssen Sie die erste Zeile des Ausdrucks kommentieren, um ihn zu unterdrücken. Die hier hinzugefügten Kommentare wirken sich auch auf die nachfolgenden Zeilen aus.
class MyClass:
def __init__(self, a: int, b: str) -> None:
self.a = a
self.b = b
MyClass(1, # type: ignore
1) #Wenn Sie den Fehler in dieser Zeile ignorieren möchten, müssen Sie ↑ kommentieren
https://github.com/python/mypy/issues/1032 Es ist in der Ausgabe aufgeführt, und eines Tages können Sie es möglicherweise Zeile für Zeile ignorieren.
Typedshed mypy kann nicht nur die Anmerkungen in der Quelle lesen, sondern auch die Stub-Datei, die den Typ definiert, und diese Methode wird für Standardbibliotheken verwendet. Ihre Stubs werden von typeshed verwaltet und wie in mypy enthalten installiert. Während des Mypy-Tests werden also illegale Aufrufe von integrierten Funktionen und Standardbibliotheken erkannt, aber der Stub selbst wird aus Python-Code generiert, und es gibt immer noch Fälle, in denen geringfügige Abweichungen bestehen. Es gibt einige Muster, die aus einer bestimmten Version der Quelle generiert werden und dem Update nicht folgen können. Daher halte ich es für eine gute Idee, eine PR zu erstellen, sobald Sie sie finden.
SqlAlchemy Es ist schwierig zu kommentieren, wenn Sie dynamisch zugeordnet werden oder eine knusprige Metaklasse verwenden. Im Fall von SqlAlchemy ist es schwierig, die Quelle direkt zu kommentieren, da "init" in der Basisklasse, die die Tabelle definiert, das Schlüsselwortargument und "setattr" empfängt. Sie können "init" in der abgeleiteten Klasse definieren, aber ich zögere, "init" in jede Tabelle für Typanmerkungen zu schreiben. Der Teil, den ich verwendet habe, war im Code in Unterpakete unterteilt, sodass ich ihn vollständig ausgeschlossen habe.
Ich bin an einem Punkt angelangt, an dem der Fehler verschwindet, selbst wenn ich "mypy -s --fast-parser
--strict-optional
ein
Optionale Typen genau prüfen. [experimentelle-strenge-optionale-Typ-und-keine-Prüfung](http://mypy.readthedocs.io/en/latest/kinds_of_types.html?highlight=strict#experimental-strict-optional-type-and-none- Überprüfung)
--Geben Sie --disallow-untyped-defs
ein
Machen Sie eine Funktion ohne Typanmerkung zu einem Fehler
--Geben Sie --disypow untyped Anrufe
ein
Machen Sie einen Funktionsaufruf ohne Typanmerkung zu einem FehlerMit diesem Gefühl denke ich, wäre es großartig, wenn wir es schrittweise zu einer strengeren Option machen könnten, während wir in CI eintauchen.