[PYTHON] mypy le fera

Ceci est l'article sur le 21ème jour du Calendrier de l'Avent KLab 2016.

J'ai envie de faire mypy, alors j'écrirai une histoire que j'ai essayée avec du code interne.

Suite

J'ai fait mypy

Qu'est-ce que mypy

La syntaxe pour l'annotation de type (PEP 484) a été ajoutée depuis python3.5. Mypy analyse statiquement le type dans le code en fonction de cette annotation.

Exemple

def func() -> int:
    return "a"

Quand je lance mypy sur un code comme celui-ci

$ mypy test.py
test.py: note: In function "func":
test.py:2: error: Incompatible return value type (got "str", expected "int")

Le type est vérifié en fonction de l'annotation et str est renvoyé même si le type de retour est int, une erreur se produit.

Sentiment d'aller

"Go mypy" signifie exécuter mypy et réussir le contrôle statique avec l'annotation de type correspondant à PEP 484. La raison pour laquelle j'ai eu envie de le faire était [Types statiques en Python, oh mon (py)!](Http://blog.zulip.org/2016/10/13/static-types-in-python-oh -mypy /) J'ai beaucoup lu cet article. J'avais l'impression que je pouvais le faire. Étant donné que le projet dont j'étais en charge était volumineux et que les fonctions existantes étaient fréquemment modifiées, j'ai pensé qu'avoir une annotation de type fiable m'aiderait à le corriger. Une autre raison est que PyCharm prend désormais en charge PEP 484 et émet un avertissement, de sorte que la partie avec le mauvais indice de type devient une erreur et devient perceptible.

ligne de départ

La base de code a été écrite en python3.5 et a été développée avec des règles lâches telles que "l'annotation est ajoutée par la personne qui le veut". Les annotations sont attachées à environ 70% des méthodes à l'exception des lots et des tests. Pour le moment mypy -s --fast-parser --strict-optional --disallow-untyped-defs --disallow-untyped-calls <dir> Quand j'ai couru, j'ai eu près de 3000 erreurs. Étant donné que le travail d'écrasement dans l'ordre du haut tel qu'il est peut être douloureux, j'ai décidé de le limiter à la partie où la logique métier est obstruée et de l'exécuter avec seulement le minimum d'options pour la corriger.

s'entendre

La version de mypy est 0.4.6 et python est 3.5.2. mypy -s --fast-parser <dir> Je l'ai réparé en courant. L'option -s est une option qui ne vérifie pas le module importé, sinon elle ira vérifier la bibliothèque tierce importée. L'importation du module dans le répertoire spécifié par <dir> fera du bon travail. Le type de l'argument et de la valeur de retour lors de l'appel de la fonction du module externe est ʻAny. L'option --fast-parserest l'option par défaut, et avec l'analyseur par défaut actuel, il existe un modèle qui provoque une erreur d'analyse lors de la décompression de l'argument. Seul--fast-parser` prend en charge la syntaxe de python3.6.

Où j'ai trébuché après avoir essayé

J'ai oublié d'annoter __init__

Il y avait tellement d'endroits dans la classe qui n'avaient pas l'annotation de type de la méthode __init__. En premier lieu, je n'étais pas conscient d'annoter «init».

Note that the return type of init ought to be annotated with -> None .

PEP484#the-meaning-of-annotations Définissons donc le type de retour sur «Aucun». Heureusement, ce modèle peut être géré mécaniquement, j'ai donc écrit un script approprié pour le gérer.

imported but unused

Il existe un outil d'analyse statique de python appelé flake8, qui est utilisé pour vérifier sur le serveur CI. Certains cas de test flake8 vérifient les instructions d'importation inutilisées, ce qui entraînera une erreur si importé mais jamais utilisé. Le problème ici est lorsque vous souhaitez annoter des variables au lieu de fonctions. En python3.5, lors de l'annotation d'une variable, elle est supposée correspondre à un commentaire. (python3.6 ajoute une nouvelle syntaxe pour ajouter des annotations de variables. PEP526)

from typing import Optional
a = None  # type: Optional[int]

J'écris des annotations variables comme celle-ci, mais comme c'est un commentaire, quand j'exécute flake8

$ flake8 ./test.py
./test.py:1:1: F401 'Optional' imported but unused

J'obtiens une erreur. Donc, j'ai ajouté # noqa et un commentaire à la partie import du module où cette erreur s'est produite pour supprimer l'erreur.

from typing import Optional  # noqa
a = None  # type: Optional[int]

Depuis python3.6

a: Optional[int] = None

Puisqu'il peut être écrit, le problème de l'importation inutilisée par annotation de variable peut être moins susceptible de se produire.

Importation circulaire

Lors de l'annotation des variables et des méthodes de module, il existe un modèle d'importation mutuelle entre les modules. Si cela se produit, une boucle se produira au moment de l'exécution et une erreur se produira, des contre-mesures sont donc nécessaires. À partir de python3.5.2, une variable appelée typing.TYPE_CHECKING est disponible, qui est créée par un tiers. Une variable qui n'est «True» que lorsque l'outil la lit, qui peut être utilisée pour l'éviter. Cette variable est utilisée pour éviter de charger le module cible uniquement lors de l'analyse côté import. La solution est également répertoriée dans la documentation officielle de mypy. 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 de # type: ignorer

Le commentaire # type: ignore peut supprimer le test de mypy, mais cette position était le compositeur. S'il y a une erreur dans une expression multiligne, vous devez commenter la première ligne de l'expression pour la supprimer. De plus, les commentaires ajoutés ici affecteront les lignes suivantes.

class MyClass:
    def __init__(self, a: int, b: str) -> None:
        self.a = a
        self.b = b

MyClass(1,  # type: ignore
        1)  #Si vous voulez ignorer l'erreur sur cette ligne, vous devez commenter ↑

https://github.com/python/mypy/issues/1032 Il est répertorié dans le numéro, et un jour, vous pourrez peut-être «ignorer» ligne par ligne.

Typedshed mypy peut lire non seulement les annotations dans la source mais également le fichier stub qui définit le type, et cette méthode est utilisée pour les bibliothèques standard. Leurs stubs sont gérés par typeshed et sont installés comme inclus dans mypy. Ainsi, pendant le test mypy, il détecte les appels illégaux aux fonctions intégrées et aux bibliothèques standard, mais le stub lui-même est généré à partir du code python et il y a encore des cas où il y a des écarts mineurs. Certains modèles sont générés à partir d'une version spécifique de la source et ne peuvent pas suivre la mise à jour, donc je pense que c'est une bonne idée de lancer un PR dès que vous le trouvez.

Problèmes non résolus

SqlAlchemy Il est difficile d'annoter si vous êtes attribué dynamiquement ou si vous utilisez une métaclasse croustillante. Dans le cas de SqlAlchemy, il est difficile d'annoter la source directement car «init» dans la classe de base qui définit la table reçoit le mot-clé argument et «setattr». Vous pouvez définir «init» dans la classe dérivée, mais j'hésite à écrire «init» dans chaque table pour l'annotation de type. La partie que j'utilise était organisée en sous-packages dans le code, donc je l'ai complètement exclue.

Essayez-le

J'en suis arrivé au point où l'erreur disparaît même si j'exécute mypy -s --fast-parser <dir>. Il y avait peu de modèles qui ne pouvaient pas être aidés, et j'ai pu les corriger avec obéissance. Il y avait un bogue de Typeshed, mais j'ai juste ajouté ʻignore, donc l'erreur n'a pas disparu et je n'ai pas pu continuer. J'ai également pu trouver des bugs. J'ai écrit le test, mais je l'ai trouvé dans un endroit que le test ne pouvait pas couvrir, comme un système anormal. Le travail de correction a été assez difficile car je ne pouvais pas déterminer le type qui était retourné à moins de lire correctement le code, donc je l'ai traité petit à petit chaque jour. Il n'y a pas d'autre choix que d'aller régulièrement. Mypy lui-même est encore en développement, mais je ne pense pas qu'il y ait quoi que ce soit que nous ne puissions faire pour le moment. Il est clair que c'est utile pour lire le code, en particulier les annotations sur les variables que vous ne savez pas quoi mettre, comme ʻa = [] . C'est aussi agréable de pouvoir savoir où vous faites un mauvais appel avant de tester. Je ne sais pas si cela sera utile pour la refactorisation, mais il sera plus facile de comprendre les fonctions qui renvoient des types trop complexes, il semble donc que vous ferez attention lors de l'écriture.

Que faire à l'avenir

Avec ce sentiment, je pense que ce serait formidable si nous pouvions progressivement en faire une option plus stricte tout en plongeant dans l'IC.

Choses à lire au fur et à mesure

** mypy le fera !! **

Recommended Posts

mypy le fera
Je veux le faire avec Python lambda Django, mais je vais m'arrêter
J'ai fait des recherches sur Docker, donc je vais le résumer