Vérification de type statique qui démarre vaguement en Python

J'ai aimé écrire TypeScript pendant environ un an, mais j'ai commencé à utiliser Python3 pour les entreprises il y a environ un mois. Python est simple et amusant, mais je l'ai hérité des autres, il est difficile à développer et je n'ai aucun type.

La partie la plus difficile est la lecture de code, que renvoie cette fonction? Je ne sais pas en un coup d'œil ce que contient cette variable. …… Il n'y a pas de type comme document. S'il n'existe pas, j'ai décidé de l'introduire, j'ai donc décidé d'introduire l'annotation de type et mypy.

Même si vous introduisez soudainement le moule à la robustesse, ce sera douloureux au contraire, j'ai donc pris la méthode d'introduction du moule sans en faire trop de l'état sans moule, et j'ai pu l'introduire assez bien, donc cette fois la procédure à ce moment-là et les connaissances obtenues , Je voudrais vous présenter quelques conseils.

environnement

Cette fois, je teste dans un environnement pipenv, mais je pense que mypy ne change pas même avec pip etc.

python3.7.5 pipenv, version 2018.11.26

Veuillez vous référer ici pour l'introduction de pipenv https://pipenv-ja.readthedocs.io/ja/translate-ja/index.html

Installation

Installez mypy dans le répertoire du projet.

cd ./myproject
pipenv install mypy -d

Vérification du type statique

Si mypy est installé dans le pip global et que vous voulez taper statiquement le code sous le répertoire src, vous pouvez taper avec mypy. / Src, mais si mypy est uniquement dans pipenv, vous obtiendrez une erreur. Devenir.

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

sudo apt install mypy

Si vous entrez dans l'environnement virtuel de pipenv, vous pouvez l'exécuter sans aucun problème.

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

Il est difficile d'entrer dans l'environnement virtuel à chaque fois, alors enregistrons le script dans Pipfile.

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

Référence https://pipenv-ja.readthedocs.io/ja/translate-ja/advanced.html#custom-script-shortcuts

Puisque la commande exécutée par le script est exécutée dans l'environnement de pipenv, mypy peut être exécuté sans exécuter pipenv shell.

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

Utilisez cette commande lors de la vérification de type, par exemple dans CI.

Python 3 intégré

Je pense que la plupart des gens le savent, mais passons en revue les types de base. Parmi les documents officiels, je pense que les 8 types suivants sont tout au plus utilisés dans l'inspection de type normale. https://docs.python.org/ja/3.7/library/stdtypes.html

Si vous vous perdez, vous pouvez le mettre dans le type de fonction intégré () et voir le résultat, de sorte que vous n'ayez même pas à vous en souvenir.

Type de type Nom du modèle Exemple
Type booléen bool True
Type entier int 10
Type à virgule flottante float 1.2
Type de séquence de texte (type chaîne de caractères) str 'hoge'
Type de liste list [1, 2, 3]
Type de taple tuple ('a', 'b')
Type de dictionnaire (type de mappage) dict { 'a': 'hoge', 'b': 'fuga'}
Type collectif set { 'j', 'k', 'l'}

Essayez de saisir une fonction non typée

Commençons par créer une fonction non typée. Créez my_module.py sous ./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'))

J'ai essayé d'en faire une fonction qui reçoit l'heure de 0 à 24 et renvoie un message d'accueil. Maintenant, quand j'exécute une vérification de type ...

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

Pas d'erreur! La raison en est que puisque l'annotation de type n'est pas effectuée, la valeur de retour et l'argument de la fonction seront le type de base Any (type de tout). (S'il y a une base de code existante, je pense que c'est bien car cela ne provoque pas d'erreur lors de l'introduction du type et cela ne me brise pas le cœur.) Ensuite, ajoutons une annotation de type.

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

Ajout d'une annotation de type sur la première ligne pour indiquer "recevoir un type entier et renvoyer un type chaîne". Essayez de réexécuter la vérification de type dans cet état.

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

Cette fois, j'ai une erreur correctement. Quand j'ai lu le message d'erreur, j'ai passé la chaîne de caractères lors de l'appel de la fonction get_greeting sur la 14e ligne. Si vous l'exécutez tel quel, une erreur d'exécution se produira. Si vous modifiez le code pour passer un type entier et effectuez à nouveau la vérification de type, l'erreur disparaîtra.

    print(get_greeting(10))

En ajoutant des annotations de type, nous avons pu rendre le code plus facile à comprendre et éviter les erreurs d'exécution.

Utilisez le fichier de configuration mypy.ini

Même dans ce cas, vous souhaitez parfois forcer les annotations de type. Dans ce cas, créez un fichier de paramètres.

mypy.ini


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

Modifiez le script pour spécifier également le fichier de configuration.

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

En faisant cela, si vous oubliez d'ajouter l'annotation de type, une erreur sera renvoyée.

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

Référence https://mypy.readthedocs.io/en/latest/config_file.html

Techniques que vous souhaitez utiliser pour introduire librement

Bien qu'il puisse être installé dans une base de code existante avec une tolérance Any, il est inévitable qu'un grand nombre d'erreurs se produise lors de l'installation si la base de code d'origine est volumineuse. Veuillez attendre un moment avant que votre cœur soit sur le point de se briser. 90% des erreurs devraient disparaître simplement en exécutant les deux suivantes.

Référence https://mypy.readthedocs.io/en/latest/existing_code.html#start-small

Ignorer les importations de modules non typés

Par exemple, si vous avez le code suivant:

import request

Lorsque j'effectue une vérification de type, j'obtiens une erreur de 3 lignes.

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

C'est parce qu'il n'y a pas de fichier de définition de type (stub) pour le module importé.

Comme il est difficile de préparer tous les fichiers de définition de type au moment de l'installation, ** ignorez ** dans les paramètres de mypy.ini.

mypy.ini


[mypy-request.*]
ignore_missing_imports = True

Cela ignorera le manque de stub dans l'importation depuis request.

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

La paix est maintenant de retour.

Ignorez seulement cette ligne

Je ne peux pas le recommander beaucoup, mais il est souvent utilisé lorsque vous souhaitez attribuer le mauvais type de test, etc. Ajoutez un commentaire «# type: ignore» à la fin de la ligne de code que vous souhaitez ignorer.

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

Vous pouvez supprimer l'erreur de type qui aurait dû se produire sur cette ligne.

Bonus: générer automatiquement le stub

Vous pouvez dire: "Non, je veux utiliser le stub." Cependant, il n'y a aucune garantie que les développeurs de modules tiers disposent de stubs. Il est difficile de le fabriquer soi-même. Dans un tel cas, générons-le automatiquement.

Vous pouvez générer automatiquement un stub en spécifiant un fichier ou un répertoire avec la commande stubgen, qui peut être utilisé en insérant mypy.

$ stubgen foo.py bar.py

S'il s'agit d'un module importé, vous pouvez vérifier le chemin du module avec * .__ path __, vous pouvez donc également créer un stub en spécifiant directement ce chemin.

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

Une fois que vous connaissez le chemin, exécutez stubgen.

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

L'exécution de stubgen crée un répertoire out dans la racine du projet, spécifiez donc ce chemin dans mypy.ini afin que mypy puisse le voir.

mypy.ini


[mypy]
python_version = 3.7
mypy_path = ./out

L'inspection de type est maintenant réussie.

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

Les types générés par stubgen ne sont pas parfaits. Ce sera presque n'importe quel type, donc si vous voulez l'utiliser sérieusement, vous devez modifier le fichier stub vous-même.

Référence https://github.com/python/mypy/blob/master/docs/source/stubgen.rst

Type avancé fréquemment utilisé

En plus du type intégré, il existe d'autres types qui sont souvent utilisés, je vais donc les présenter. Dans mypy, à l'exception des types intégrés, le module typing et le module typing_extensions appellent et utilisent des classes de ces types. C'est un peu différent du tapuscript, mais comme la plupart des types, y compris les types génériques, sont couverts dans ces modules, cela semble être satisfaisant pour ceux qui veulent faire de la programmation de type.

Référence https://mypy.readthedocs.io/en/latest/

Optional

Les fonctions telles que le renvoi normal d'un entier et le renvoi None si une valeur incorrecte est reçue sont courantes. La valeur de retour dans ce cas est int ou None, mais facultatif peut l'exprimer.

from typing import Optional

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

List, Dict Une liste d'entiers, une liste de chaînes, etc. peut être représentée par List.

from typing import List

#Liste d'entiers
intList: List[int] = [1, 2, 3, 4] 

#Liste des chaînes
strList: List[str] = ['a', 'b', 'c']

De même, si vous utilisez Dict, vous pouvez exprimer quelque chose comme "la clé est un caractère et la valeur est un entier" même dans un type de dictionnaire.

from typing import Dict

#Type de dictionnaire où la clé est un caractère et la valeur est un entier
strIntDict: Dict[str, int] = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Union

Vous pouvez créer un type d'union qui combine plusieurs personnes.

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

Bien sûr, tout type est également possible si vous ne souhaitez pas spécifier le type

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

Vous pouvez exprimer le type d'une fonction avec Callable.

from typing import Callable

#Définition de type de fonction qui reçoit un argument de type entier et renvoie un type chaîne
func: Callable[[int], str]

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

func = sample

TypedDict

Vous souhaiterez peut-être spécifier la valeur d'une clé dans la carte et spécifier le type de valeur de la clé. S'il s'agit d'un script typographique, il est exprimé par interface.

Par exemple, supposons que vous ayez une valeur de type dictionnaire appelée movie.

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

move a des clés appelées nom et année, mais si vous mettez accidentellement un entier dans le nom ou si vous mettez une chaîne de caractères dans l'année lors de l'écrasement, ce serait un problème. TypedDict le rend facile à exprimer en tant que type.

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

Il peut également être exprimé sous la forme d'une classe. Personnellement, je préfère cela car cela ressemble à une interface TS.

from typing_extensions import TypedDict

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

Veuillez consulter le document officiel pour plus de détails. https://mypy.readthedocs.io/en/latest/more_types.html#typeddict

Résumé

Qu'as-tu pensé? J'espère que vous pensez que démarrer un type en Python est un obstacle étonnamment faible.

Si vous avez du mal sans type en Python, introduisons-le maintenant! C'est plus que ce à quoi je m'attendais, donc je peux être heureux.

Le mécontentement est que le soutien de l'éditeur n'est pas très solide. J'utilise Pyright comme extension de VS-Code, mais j'aimerais changer s'il y a quelque chose de mieux.

Bonne fin d'année!

Recommended Posts

Vérification de type statique qui démarre vaguement en Python
Définition du type d'argument de fonction en python
[Français] Type statique Python, incroyable mypy!
Charger dynamiquement les types json avec python
Type spécifié en python. Lancer des exceptions
Tapez les annotations pour Python2 dans les fichiers stub!
Dessiner des lignes de contour qui apparaissent dans les manuels (Python)
Un mémo que j'ai écrit un tri rapide en Python
Créer un environnement qui utilise Python avec Eclipse
Obtenez plusieurs clés maximales dans le type de dictionnaire Python
Un programme qui supprime les instructions en double en Python
Méthodes de test qui renvoient des valeurs aléatoires en Python
Celui qui affiche la barre de progression en Python
Comment gérer le type datetime dans sqlite3 de python
Formules qui apparaissent dans Faire des mathématiques avec Python
Quadtree en Python --2
Python en optimisation
CURL en Python
Métaprogrammation avec Python
Python 3.3 avec Anaconda
Géocodage en python
SendKeys en Python
Méta-analyse en Python
Unittest en Python
Époque en Python
Allemand en Python
DCI en Python
tri rapide en python
nCr en python
N-Gram en Python
Programmation avec Python
python commence par ()
Plink en Python
Constante en Python
FizzBuzz en Python
Sqlite en Python
Étape AIC en Python
LINE-Bot [0] en Python
CSV en Python
Assemblage inversé avec Python
Réflexion en Python
Type de chaîne Python2
Constante en Python
Python # type de chaîne
nCr en Python.
format en python
Scons en Python 3
Puyopuyo en python
python dans virtualenv
PPAP en Python
Quad-tree en Python
Réflexion en Python
Chimie avec Python
Hashable en Python
DirectLiNGAM en Python
LiNGAM en Python
Aplatir en Python
Aplatir en python