Statische Typprüfung, die in Python lose beginnt

Ich habe ungefähr ein Jahr lang gerne TypeScript geschrieben, aber vor ungefähr einem Monat habe ich angefangen, Python3 für geschäftliche Zwecke zu verwenden. Python ist einfach und macht Spaß, aber ich habe es von anderen geerbt, und es ist schwer zu entwickeln und hat keinen Typ.

Der schwierigste Teil ist das Lesen von Code. Was gibt diese Funktion zurück? Ich weiß nicht auf einen Blick, was in dieser Variablen enthalten ist. …… Es gibt keinen Typ als Dokument. Wenn es nicht existiert, habe ich beschlossen, es einzuführen, also habe ich beschlossen, Typanmerkungen und mypy einzuführen.

Selbst wenn Sie die Form plötzlich in die Robustheit einführen, wird es im Gegenteil schmerzhaft sein. Deshalb habe ich die Methode zum Einführen der Form gewählt, ohne sie aus dem Zustand ohne die Form zu übertreiben, und ich konnte sie recht gut einführen, also diesmal das Verfahren zu diesem Zeitpunkt und das gewonnene Wissen Ich möchte einige Tipps vorstellen.

Umgebung

Dieses Mal teste ich in einer Pipenv-Umgebung, aber ich glaube nicht, dass es einen besonderen Unterschied zwischen Mypy und Pip usw. gibt.

python3.7.5 pipenv, version 2018.11.26

Die Einführung von pipenv finden Sie hier https://pipenv-ja.readthedocs.io/ja/translate-ja/index.html

Installation

Installieren Sie mypy im Projektverzeichnis.

cd ./myproject
pipenv install mypy -d

Statische Typprüfung durchführen

Wenn mypy im globalen Pip installiert ist und Sie den Code im Verzeichnis src statisch überprüfen möchten, können Sie mit mypy. / Src tippen. Wenn sich mypy jedoch nur in pipenv befindet, wird eine Fehlermeldung angezeigt. Werden.

$ mypy ./src
Command 'mypy' not found, but can be installed with:

sudo apt install mypy

Wenn Sie die virtuelle Umgebung von pipenv aufrufen, können Sie diese problemlos ausführen.

$ pipenv shell
(myproject) $ mypy ./src
Success: no issues found in 2 source files

Es ist schwierig, jedes Mal in die virtuelle Umgebung zu gelangen. Registrieren Sie das Skript daher in Pipfile.

[scripts]
type-check = "mypy ./src"

Referenz https://pipenv-ja.readthedocs.io/ja/translate-ja/advanced.html#custom-script-shortcuts

Da der vom Skript ausgeführte Befehl in der Umgebung von pipenv ausgeführt wird, kann mypy ausgeführt werden, ohne "pipenv shell" auszuführen.

$ pipenv run type-check
mypy.ini: No [mypy] section in config file
Success: no issues found in 1 source files

Verwenden Sie diesen Befehl, wenn Sie eine Typprüfung durchführen, z. B. in CI.

Eingebautes Python 3

Ich denke, die meisten Leute wissen es, aber lassen Sie uns die Grundtypen überprüfen. Von den offiziellen Dokumenten denke ich, dass die folgenden 8 Typen höchstens bei der normalen Typprüfung verwendet werden. https://docs.python.org/ja/3.7/library/stdtypes.html

Wenn Sie sich verlaufen, können Sie es in den integrierten Funktionstyp () einfügen und das Ergebnis anzeigen, sodass Sie sich nicht einmal daran erinnern müssen.

Art des Typs Modellname Beispiel
Boolescher Typ bool True
Ganzzahliger Typ int 10
Gleitkomma-Typ float 1.2
Textsequenztyp (Zeichenkettentyp) str 'hoge'
Listentyp list [1, 2, 3]
Taple-Typ tuple ('a', 'b')
Wörterbuchtyp (Zuordnungstyp) dict { 'a': 'hoge', 'b': 'fuga'}
Kollektiver Typ set { 'j', 'k', 'l'}

Versuchen Sie, eine untypisierte Funktion einzugeben

Lassen Sie uns zunächst eine untypisierte Funktion erstellen. Erstellen Sie "my_module.py" unter ./src.

my_module.py


def get_greeting(time):
  if 4 <= time < 10:
    return 'Good morning!'
  elif 10 <= time < 14:
    return 'Hello!'
  elif 14 <= time < 24:
    return 'Goog afternoon.'
  elif 0 <= time < 4:
    return 'zzz..'
  else:
    return ''


if __name__ == "__main__":
    print(get_greeting('morning'))

Ich habe versucht, daraus eine Funktion zu machen, die die Zeit von 0 bis 24 empfängt und eine Begrüßung zurückgibt. Wenn ich jetzt eine Typprüfung durchführe ...

$ pipenv run type-check
Success: no issues found in 1 source file

Kein Fehler! Der Grund dafür ist, dass der Rückgabewert und das Argument der Funktion der grundlegende Any-Typ (Typ von irgendetwas) sind, da keine Typanmerkung ausgeführt wird. (Wenn es eine vorhandene Codebasis gibt, denke ich, dass dies in Ordnung ist, da es beim Einführen des Typs keinen Fehler verursacht und mein Herz nicht bricht.) Als nächstes fügen wir eine Typanmerkung hinzu.

def get_greeting(time: int) -> str:
  if 4 <= time < 10:
    return 'Good morning!'
  elif 10 <= time < 14:
    return 'Hello!'
  elif 14 <= time < 20:
    return 'Goog afternoon.'
  elif 0 <= time < 4:
    return 'zzz..'
  else:
    return None

if __name__ == "__main__":
    print(get_greeting('morning'))

In der ersten Zeile wurde eine Typanmerkung hinzugefügt, die angibt, dass "ein ganzzahliger Typ empfangen und ein Zeichenfolgentyp zurückgegeben werden soll". Versuchen Sie in diesem Zustand erneut, die Typprüfung durchzuführen.

$ pipenv run type-check
src/my_module.py:14: error: Argument 1 to "get_greeting" has incompatible type "str"; expected "int"
Found 1 error in 1 file (checked 1 source file)

Diesmal habe ich einen Fehler richtig bekommen. Beim Lesen der Fehlermeldung habe ich beim Aufrufen der Funktion get_greeting in der 14. Zeile die Zeichenfolge übergeben. Wenn Sie es so ausführen, wie es ist, tritt ein Laufzeitfehler auf. Wenn Sie den Code so ändern, dass ein ganzzahliger Typ übergeben und die Typprüfung erneut durchgeführt wird, verschwindet der Fehler.

    print(get_greeting(10))

Durch Hinzufügen von Typanmerkungen konnten wir den Code verständlicher machen und Laufzeitfehler vermeiden.

Verwenden Sie die Konfigurationsdatei mypy.ini

Trotzdem gibt es Zeiten, in denen Sie Typanmerkungen erzwingen möchten. Erstellen Sie in diesem Fall eine Einstellungsdatei.

mypy.ini


[mypy]
python_version = 3.7
disallow_untyped_calls = True
disallow_untyped_defs = True

Ändern Sie das Skript, um auch die Konfigurationsdatei anzugeben.

[scripts]
type-check = "mypy ./src --config-file ./mypy.ini"

Wenn Sie auf diese Weise vergessen, die Typanmerkung hinzuzufügen, wird ein Fehler zurückgegeben.

$ pipenv run type-check
src/my_module.py:1: error: Function is missing a type annotation
src/my_module.py:14: error: Call to untyped function "get_greeting" in typed context
Found 2 errors in 1 file (checked 1 source file)

Referenz https://mypy.readthedocs.io/en/latest/config_file.html

Techniken, die Sie verwenden möchten, um sie lose einzuführen

Obwohl es mit einer beliebigen Toleranz in einer vorhandenen Codebasis installiert werden kann, ist es unvermeidlich, dass während der Installation eine große Anzahl von Fehlern auftritt, wenn die ursprüngliche Codebasis groß ist. Bitte warten Sie einen Moment, bevor Ihr Herz bricht. 90% der Fehler sollten durch Ausführen der folgenden beiden Fehler verschwinden.

Referenz https://mypy.readthedocs.io/en/latest/existing_code.html#start-small

Ignorieren Sie Importe von untypisierten Modulen

Zum Beispiel, wenn Sie den folgenden Code haben:

import request

Wenn ich eine Typprüfung durchführe, erhalte ich einen Fehler von 3 Zeilen.

$ pipenv run type-check
src/my_module.py:1: error: Cannot find implementation or library stub for module named 'request'
src/my_module.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
Found 1 error in 1 file (checked 1 source file)

Dies liegt daran, dass für das importierte Modul keine Typdefinitionsdatei (Stub) vorhanden ist.

Da es zum Zeitpunkt der Installation schwierig ist, alle Typdefinitionsdateien vorzubereiten, ** ignorieren ** Sie in den Einstellungen von mypy.ini.

mypy.ini


[mypy-request.*]
ignore_missing_imports = True

Dies ignoriert das Fehlen eines Stubs beim Import von "Anfrage".

$ pipenv run type-check
Success: no issues found in 1 source file

Der Frieden ist jetzt zurück.

Ignoriere nur diese Zeile

Ich kann es nicht sehr empfehlen, aber es wird oft verwendet, wenn Sie im Test usw. den falschen Typ zuweisen möchten. Fügen Sie am Ende der Codezeile, die Sie ignorieren möchten, einen Kommentar "# type: ignore" hinzu.

    print(get_greeting('hoge'))  #type: ignore

Sie können den Typfehler unterdrücken, der in dieser Zeile hätte auftreten sollen.

Bonus: Stub automatisch generieren

Sie können sagen: "Nein, ich möchte Stub verwenden." Es gibt jedoch keine Garantie dafür, dass den Entwicklern von Modulen von Drittanbietern Stubs zur Verfügung stehen. Es ist mühsam, es selbst zu machen. In einem solchen Fall generieren wir es automatisch.

Sie können automatisch einen Stub generieren, indem Sie mit dem Befehl stubgen eine Datei oder ein Verzeichnis angeben, die bzw. das durch Einfügen von mypy verwendet werden kann.

$ stubgen foo.py bar.py

Wenn es sich um ein importiertes Modul handelt, können Sie den Pfad des Moduls mit * .__ path __ überprüfen, sodass Sie auch einen Stub erstellen können, indem Sie diesen Pfad direkt angeben.

>>> import request
>>> request.__path__
['/home/username/.local/share/virtualenvs/myproject-xxxxxxxx/lib/python3.7/site-packages/request']
>>>

Sobald Sie den Pfad kennen, führen Sie stubgen aus.

(myproject) $ stubgen /home/username/.local/share/virtualenvs/myproject-xxxxxxxx/lib/python3.7/site-packages/request
Processed 1 modules
Generated out/request/__init__.pyi

Wenn Sie stubgen ausführen, wird ein Out-Verzeichnis im Projektstamm erstellt. Geben Sie diesen Pfad in mypy.ini an, damit mypy ihn sehen kann.

mypy.ini


[mypy]
python_version = 3.7
mypy_path = ./out

Die Typprüfung ist jetzt bestanden.

$ pipenv run type-check
Success: no issues found in 1 source file

Die von stubgen erzeugten Typen sind nicht perfekt. Es wird fast jeder Typ sein. Wenn Sie es also ernsthaft verwenden möchten, müssen Sie die Stub-Datei selbst ändern.

Referenz https://github.com/python/mypy/blob/master/docs/source/stubgen.rst

Häufig verwendeter fortgeschrittener Typ

Zusätzlich zum eingebauten Typ gibt es andere Typen, die häufig verwendet werden, daher werde ich sie vorstellen. In mypy rufen mit Ausnahme der eingebauten Typen das Modul "typing" und das Modul "typing_extensions" Klassen dieser Typen auf und verwenden sie. Es unterscheidet sich ein wenig von Typoskript, aber da die meisten Typen, einschließlich generischer Typen, in diesen Modulen behandelt werden, scheint es für diejenigen, die Typprogrammierung durchführen möchten, zufriedenstellend zu sein.

Referenz https://mypy.readthedocs.io/en/latest/

Optional

Funktionen wie das normale Zurückgeben einer Ganzzahl und das Zurückgeben von None, wenn ein falscher Wert empfangen wird, sind häufig. Der Rückgabewert ist in diesem Fall int oder None, aber Optional kann dies ausdrücken.

from typing import Optional

def sample(time: int) -> Optional[int]:
  if 24 < time:
    return None
  else:
    return time

List, Dict Eine Liste von Ganzzahlen, eine Liste von Zeichenfolgen usw. kann durch Liste dargestellt werden.

from typing import List

#Liste der ganzen Zahlen
intList: List[int] = [1, 2, 3, 4] 

#Liste der Zeichenfolgen
strList: List[str] = ['a', 'b', 'c']

Wenn Sie Dict verwenden, können Sie auch in einem Wörterbuchtyp etwas wie "Schlüssel ist ein Zeichen und Wert ist eine Ganzzahl" ausdrücken.

from typing import Dict

#Wörterbuchtyp, bei dem der Schlüssel ein Zeichen und der Wert eine Ganzzahl ist
strIntDict: Dict[str, int] = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Union

Sie können einen Gewerkschaftstyp erstellen, der mehrere Personen kombiniert.

from typing import Union

strOrInt: Union[str, int] = 1  # OK
strOrInt = 'hoge'  # OK
strOrInt = None # error: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int]")

Any

Natürlich ist auch jeder Typ möglich, wenn Sie den Typ nicht angeben möchten

from typing import Any

string: str = 'hoge' 

any: Any = string
any = 10  # OK

notAny = string
notAny = 10  # error: Incompatible types in assignment (expression has type "int", variable has type "str")

Callable

Sie können den Typ einer Funktion mit Callable ausdrücken.

from typing import Callable

#Funktionstypdefinition, die ein Ganzzahlargument empfängt und einen Zeichenfolgentyp zurückgibt
func: Callable[[int], str]

def sample(num: int) -> str:
  return str(num)

func = sample

TypedDict

Möglicherweise möchten Sie den Wert eines Schlüssels in der Zuordnung angeben und angeben, welchen Werttyp der Schlüssel hat. Wenn es sich um Typoskript handelt, wird es durch die Schnittstelle ausgedrückt.

Angenommen, Sie haben einen Wörterbuchwert namens movie.

movie = {'name': 'Blade Runner', 'year': 1982}

move hat Schlüssel mit den Namen name und year. Wenn Sie jedoch beim Überschreiben versehentlich eine Ganzzahl in den Namen oder eine Zeichenfolge in das Jahr eingeben, ist dies ein Problem. TypedDict erleichtert das Ausdrücken als Typ.

from typing_extensions import TypedDict

Movie = TypedDict('Movie', {'name': str, 'year': int})

movie1: Movie = {'name': 'Blade Runner', 'year': 1982} # OK

movie2: Movie = {'name': 'Blade Runner', 'year': '1982'} # error: Incompatible types (expression has type "str", TypedDict item "year" has type "int")

Es kann auch in Form einer Klasse ausgedrückt werden. Persönlich bevorzuge ich dies, weil es wie eine TS-Schnittstelle aussieht.

from typing_extensions import TypedDict

class Movie(TypedDict):
    name: str
    year: int

Bitte überprüfen Sie das offizielle Dokument für Details. https://mypy.readthedocs.io/en/latest/more_types.html#typeddict

Zusammenfassung

Was haben Sie gedacht? Ich hoffe, Sie glauben, dass das Starten eines Typs in Python eine überraschend niedrige Hürde ist.

Wenn Sie Schwierigkeiten haben, Python nicht einzugeben, stellen wir es jetzt vor! Es ist mehr als ich erwartet hatte, also kann ich glücklich sein.

Die Unzufriedenheit ist, dass die Unterstützung des Herausgebers nicht sehr solide ist. Ich verwende Pyright als Erweiterung von VS-Code, möchte aber wechseln, wenn es etwas Besseres gibt.

Habt ein schönes Jahresende!

Recommended Posts

Statische Typprüfung, die in Python lose beginnt
Definition des Funktionsargumenttyps in Python
[Übersetzung] Python statischer Typ, erstaunlicher Mypy!
Laden Sie JSON-Typen dynamisch mit Python
Typ in Python angegeben. Ausnahmen auslösen
Geben Sie Anmerkungen für Python2 in Stub-Dateien ein!
Zeichnen Sie Konturlinien, die in Lehrbüchern erscheinen (Python).
Ein Memo, das ich schnell in Python geschrieben habe
Erstellen einer Umgebung, die Python mit Eclipse verwendet
Holen Sie sich mehrere maximale Schlüssel im Python-Wörterbuchtyp
Ein Programm, das doppelte Anweisungen in Python entfernt
Testmethoden, die zufällige Werte in Python zurückgeben
Diejenige, die den Fortschrittsbalken in Python anzeigt
Wie man mit dem Datum / Uhrzeit-Typ in Pythons SQLite3 umgeht
Formeln, die unter Mathematik mit Python ausführen angezeigt werden
Quadtree in Python --2
Python in der Optimierung
CURL in Python
Metaprogrammierung mit Python
Python 3.3 mit Anaconda
Geokodierung in Python
SendKeys in Python
Metaanalyse in Python
Unittest in Python
Epoche in Python
Deutsch in Python
DCI in Python
Quicksort in Python
nCr in Python
N-Gramm in Python
Programmieren mit Python
Python beginnt mit ()
Plink in Python
Konstante in Python
FizzBuzz in Python
SQLite in Python
Schritt AIC in Python
LINE-Bot [0] in Python
CSV in Python
Reverse Assembler mit Python
Reflexion in Python
Python2-Zeichenfolgentyp
Konstante in Python
Python # String-Typ
nCr in Python.
Format in Python
Scons in Python 3
Puyopuyo in Python
Python in Virtualenv
PPAP in Python
Quad-Tree in Python
Reflexion in Python
Chemie mit Python
Hashbar in Python
DirectLiNGAM in Python
LiNGAM in Python
In Python reduzieren
In Python flach drücken