La façon de taper la vérification de 4 millions de lignes de code Python. partie 1

Aujourd'hui, nous vous proposons la première partie de la traduction des documents sur la façon de traiter avec le contrôle de Dropbox types Python-code.



Dropbox écrit beaucoup en Python. C'est un langage que nous utilisons très largement - à la fois pour les services backend et les applications clientes de bureau. Nous utilisons également Go, TypeScript et Rust en gros volumes, mais Python est notre langage principal. Si l'on considère notre portée, et nous parlons de millions de lignes de code Python, il semble que dynamique tapant ce code compliquer inutilement la compréhension et commencer à affecter sérieusement la productivité du travail. Pour pallier ce problème, nous avons commencé le transfert progressif de notre code à un type statique avec vérification mypy. Ceci est probablement la plupart des types populaires de système d'auto-contrôle pour Python. Mypy est un projet open source; ses principaux développeurs travaillent chez Dropbox.

Dropbox a été l'une des premières sociétés à implémenter la vérification de type statique dans le code Python à une échelle similaire. De nos jours, mypy est utilisé dans des milliers de projets. Cet outil est innombrable, comme on dit, «testé au combat». Pour arriver là où nous en sommes, nous avons dû faire un long chemin. Sur cette voie, de nombreuses entreprises ont échoué et des expériences ont échoué. Ce matériel concerne l'histoire de la vérification de type statique en Python - depuis son début très difficile, qui faisait partie de mon projet de recherche scientifique, jusqu'à nos jours, lorsque les vérifications de type et les indices de type sont devenus familiers à d'innombrables développeurs qui écrivent en Python. Ces mécanismes sont désormais pris en charge par une variété d'instruments - tels que les analyseurs IDE et le code.

Lire la deuxième partie

Pourquoi vérifier le type?


Si vous avez déjà utilisé Python dynamiquement typé, vous pouvez avoir une certaine confusion quant à la raison pour laquelle un tel buzz a récemment été créé autour de la frappe statique et de mypy. Ou il se peut que vous aimiez Python précisément en raison de son typage dynamique, et ce qui se passe vous dérange. La clé de la valeur du typage statique est l'échelle des décisions: plus votre projet est grand, plus vous avez tendance à taper statiquement et, en fin de compte, plus vous en avez vraiment besoin.

Supposons qu'un projet a atteint une taille de dizaines de milliers de lignes, et il est avéré que plusieurs programmeurs travaillent. Compte tenu d'un tel projet, sur la base de notre expérience, nous pouvons dire que la compréhension de son code sera la clé pour soutenir la productivité des développeurs. Sans annotations de type, il n'est pas facile de déterminer, par exemple, quels arguments passer à une fonction ou quelles valeurs de quels types une fonction peut renvoyer. Voici des questions typiques auxquelles il est souvent difficile de répondre sans utiliser d'annotations de type:

  • Cette fonction peut-elle renvoyer None ?
  • Quel devrait être cet argument des items ?
  • Quel est le type d'attribut id : int , est-ce str ou peut-être un type personnalisé?
  • Cet argument devrait-il être une liste? Est-il possible d'y passer un tuple?

Si vous regardez l'extrait de code suivant, équipé d'annotations de type, et essayez de répondre à ces questions, il s'avère que c'est la tâche la plus simple:

 class Resource:    id: bytes    ...    def read_metadata(self,                      items: Sequence[str]) -> Dict[str, MetadataItem]:        ... 

  • read_metadata ne read_metadata pas None , car le type de retour n'est pas Optional[…] .
  • L'argument items est une séquence de chaînes. Il ne peut être répété dans aucun ordre.
  • Attribut id - ceci est une chaîne d'octets.

Dans un monde idéal, on pourrait s'attendre à ce que toutes ces subtilités soient décrites dans la documentation intégrée (docstring). Mais l'expérience donne de nombreux exemples du fait qu'une telle documentation dans le code avec lequel vous devez travailler n'est souvent pas observée. Même si une telle documentation est présente dans le code, on ne peut pas compter sur son exactitude absolue. Cette documentation peut être imprécise, inexacte, laissant beaucoup de possibilités de malentendu. Dans les grandes équipes ou dans les grands projets, ce problème peut devenir extrêmement aigu.

Bien que Python fonctionne bien aux stades précoces ou intermédiaires des projets, à un moment donné, les projets réussis et les entreprises qui utilisent Python peuvent se poser une question vitale: «Avons-nous besoin de tout réécrire dans un langage typé statiquement?»

Les systèmes de vérification de type comme mypy résolvent le problème susmentionné en fournissant au développeur un langage formel pour décrire les types, et en vérifiant que les descriptions de type sont cohérentes avec les implémentations du programme (et, éventuellement, en vérifiant leur existence). En général, nous pouvons dire que ces systèmes nous donnent une sorte de vérifier minutieusement la documentation.

L'utilisation de tels systèmes présente d'autres avantages, et ils sont déjà totalement non triviaux:

  • Un système de vérification de type peut détecter de petites erreurs (mais pas très petites). Un exemple typique - est quand vous oubliez d'analyser la valeur None ou d'autres conditions particulières.
  • La refactorisation de code est grandement simplifiée car le système de vérification de type signale souvent très précisément quel code doit être modifié. Dans ce cas, nous n'avons pas besoin d'espérer une couverture à 100% du code avec des tests, ce qui, en tout cas, est généralement impossible. Nous n'avons pas besoin d'examiner la profondeur des rapports de trace de pile afin de trouver la cause du problème.
  • peuvent souvent même de grands projets passer les types d'analyse mypy complet en une fraction de seconde. Et l'exécution des tests prend généralement des dizaines de secondes, voire des minutes. Le système de vérification de type donne au programmeur une rétroaction instantanée et lui permet de faire son travail plus rapidement. Il n'a plus besoin d'écrire des tests unitaires fragiles et lourds en support qui remplacent les entités réelles par des mokas et des correctifs juste pour obtenir les résultats des tests de code plus rapidement.

Les IDE et les éditeurs, tels que PyCharm ou Visual Studio Code, utilisent des fonctionnalités d'annotation de type pour fournir aux développeurs la possibilité de compléter automatiquement le code, de mettre en évidence les erreurs et de prendre en charge les constructions de langage couramment utilisées. Et ce ne sont que quelques-uns des avantages de la frappe. Pour certains programmeurs, tout cela est le principal argument en faveur de la frappe. C'est ce qui profite immédiatement après la mise en œuvre. Ce cas d'utilisation de type ne nécessite pas de système de vérification de type distinct, tel que mypy, bien qu'il convient de noter que mypy permet de maintenir la cohérence entre les annotations de type et le code.

contexte mypy


L'histoire de mypy a commencé au Royaume-Uni, à Cambridge, quelques années avant de rejoindre Dropbox. Dans le cadre de ma recherche doctorale, j'ai traité de l'unification des langages statiquement typés et dynamiques. Je me suis inspiré par un article sur la saisie progressive Jeremy SIECA et Walid Taha, ainsi que dactylographié projet Racket. J'ai essayé de trouver des moyens d'utiliser le même langage de programmation pour divers projets - des petits scripts aux bases de code composées de plusieurs millions de lignes. En même temps, j'aimerais qu'un projet de toute envergure ne doive pas faire de trop gros compromis. Une partie importante de tout cela est l'idée d'une transition progressive à partir des données brutes du prototype de projet bien testé produit final statiquement typé. Aujourd'hui, ces idées sont largement tenues pour acquises, mais en 2010, c'était un problème qui était encore activement exploré.

Mon travail initial dans le domaine de la vérification de type ne visait pas Python. Au lieu de cela, je une petite langue « maison » Alore . Voici un exemple qui vous permettra de comprendre - ce qui est en jeu (types d'annotations sont facultatives):

 def Fib(n as Int) as Int  if n <= 1    return n  else    return Fib(n - 1) + Fib(n - 2)  end end 

L'utilisation d'un langage simplifié de notre propre conception est une approche courante utilisée dans la recherche scientifique. Cela est dû notamment au fait que cela vous permet de réaliser rapidement des expériences, et également au fait que ce qui n'a rien à voir avec la recherche peut être librement ignoré. Les langages de programmation réellement utilisés sont généralement des phénomènes à grande échelle avec des implémentations complexes, ce qui ralentit les expériences. Cependant, tout résultat basé sur une langue simplifiée semble un peu suspect, car lorsque les résultats ont été obtenus, le chercheur peut avoir sacrifié des considérations importantes pour l'utilisation pratique des langues.

Mon outil de vérification de type pour Alore semblait très prometteur, mais je voulais le tester en expérimentant avec du vrai code, qui, pourrait-on dire, n'était pas écrit sur Alore. Heureusement, Alore était largement basé sur les mêmes idées que Python. Il était assez facile de modifier les moyens de vérifier le type afin qu'il puisse travailler avec la syntaxe et la sémantique Python. Cela nous a permis d'essayer la vérification de type dans le code Python open source. De plus, je l'ai écrit transpayler au code de conversion écrit en Python-Alore dans le code et l'a utilisé pour traduire mes moyens de code pour vérifier les types. Maintenant, j'avais un système de vérification de type écrit en Python qui supportait un sous-ensemble de Python, une sorte de langage! (Certaines solutions architecturales qui ont du sens pour Alore, mal adapté à Python, il est encore évident dans certaines parties de la base de code mypy.)

En fait, le langage pris en charge par mon système de type à ce moment-là ne pouvait pas s'appeler Python: c'était une variante Python en raison de certaines limitations de la syntaxe de la syntaxe d'annotation de type Python 3.

Il ressemblait à un mélange de Java et Python:

 int fib(int n):    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

Une de mes idées à l'époque était d'utiliser des annotations de type pour améliorer les performances en compilant ce type de Python en C, ou éventuellement en bytecode JVM. Je me suis déplacé à l'étape de l'écriture, mais le prototype de compilateur quitté cette entreprise, puisque la vérification de type et en lui-même semble très utile.

Au final, j'ai présenté mon projet lors de la conférence PyCon 2013 à Santa Clara. J'en ai également parlé avec Guido van Rossum, le généreux dictateur permanent de Python. Il m'a convaincu de renoncer à sa propre syntaxe et d'adhérer à la syntaxe standard de Python 3. Python 3 prend en charge les fonctions d'annotation, ce qui dans mon exemple peut être réécrite comme indiqué ci-dessous, pour obtenir un programme Python normal:

 def fib(n: int) -> int:    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

J'ai dû faire des compromis (tout d'abord, je tiens à noter que j'ai inventé ma propre syntaxe pour cette raison). En particulier, Python 3.3, la dernière version de la langue à l'époque ne prend pas en charge les variables d'annotations. J'ai discuté avec Guido par e-mail des différentes possibilités de syntaxe de telles annotations. Nous avons décidé d'utiliser des commentaires de type pour les variables. Cela nous a permis d'atteindre notre objectif, mais cela semblait un peu lourd (Python 3.6 nous a donné une syntaxe plus agréable):

 products = [] # type: List[str] # Eww 

Commentaires aux types sont également utiles pour soutenir Python 2, qui ne prend pas en charge native pour les types d'annotation:

 f fib(n):    # type: (int) -> int    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

Il s'est avéré que ces compromis (et d'autres) n'avaient en fait pas beaucoup d'importance - les avantages de la frappe statique ont conduit au fait que les utilisateurs ont rapidement oublié la syntaxe pas tout à fait parfaite. Comme aucune construction de syntaxe spéciale n'a été utilisée dans le code Python dans lequel les types étaient contrôlés, les outils Python et les processus de traitement de code existants ont continué à fonctionner normalement, ce qui a grandement facilité le développement du nouvel outil par les développeurs.

Guido m'a également convaincu de rejoindre Dropbox après avoir défendu mon diplôme. Ensuite, le plaisir commence dans l'histoire mypy.

À suivre ...

Chers lecteurs! Si vous utilisez Python - s'il vous plaît nous parler des projets que vous développez une échelle dans cette langue.


Source: https://habr.com/ru/post/fr468233/


All Articles