[PYTHON] mypy wird es tun

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.

Folge

Ich habe mypy gemacht

Was ist mypy?

Die Syntax für Typanmerkungen (PEP 484) wurde seit Python3.5 hinzugefügt. Mypy analysiert statisch den Typ im Code basierend auf dieser Anmerkung.

Beispiel

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.

Gefühle zu gehen

"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.

Startlinie

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.

auskommen

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.

Wo ich nach dem Versuch gestolpert bin

Ich habe vergessen, "init" zu kommentieren

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.

Zirkularimport

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()

Position von # 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.

Ungelöste Probleme

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.

Versuch es

Ich bin an einem Punkt angelangt, an dem der Fehler verschwindet, selbst wenn ich "mypy -s --fast-parser

" ausführe. Es gab nur wenige Muster, denen nicht geholfen werden konnte, und ich konnte sie gehorsam beheben. Es gab einen Fehler von "Typeshed", aber ich konnte "Ignorieren" hinzufügen, sodass der Fehler nicht verschwand und ich nicht fortfahren konnte. Ich konnte auch einige Fehler finden. Ich habe den Test geschrieben, aber ich habe ihn an einem Ort gefunden, den der Test nicht abdecken konnte, z. B. an einem abnormalen System. Die Korrekturarbeit war ziemlich schwierig, da ich den zurückgegebenen Typ nur bestimmen konnte, wenn ich den Code richtig gelesen hatte. Deshalb habe ich mich jeden Tag nach und nach damit befasst. Es bleibt keine andere Wahl, als stetig zu gehen. Mypy selbst befindet sich noch in der Entwicklung, aber ich glaube nicht, dass wir im Moment etwas nicht tun können. Es ist offensichtlich, dass es beim Lesen des Codes hilfreich ist, insbesondere bei den Anmerkungen zu Variablen, bei denen Sie nicht wissen, was Sie eingeben sollen, z. B. "a = []". Es ist auch schön, vor dem Testen herauszufinden, wo Sie offensichtlich einen schlechten Anruf tätigen. Ich bin mir nicht sicher, ob es für das Refactoring nützlich sein wird, aber es wird einfacher sein, Funktionen zu verstehen, die übermäßig komplexe Typen zurückgeben. Es scheint also, dass Sie beim Schreiben vorsichtig sein werden.

Was ist in Zukunft zu tun?

  • In CI drücken Ich habe es auf dem CI-Server noch nicht überprüft, daher werde ich die Mitglieder informieren. --Geben Sie --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 Fehler

Mit 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.

Dinge zu lesen, wie Sie gehen

** mypy wird es tun !! **

Recommended Posts

mypy wird es tun
Ich möchte es mit Python Lambda Django machen, aber ich werde aufhören
Ich habe über Docker recherchiert und werde es zusammenfassen