Aujourd'hui, nous publions la deuxième partie d'une traduction du matériel, qui est consacrée aux annotations de type en Python.

→
La première partieComment Python prend-il en charge les types de données?
Python est un langage typé dynamiquement. Cela signifie que les types de variables utilisés ne sont vérifiés que pendant l'exécution du programme. Dans l'exemple donné dans la partie précédente de l'article, on pouvait voir qu'un programmeur qui écrit en Python n'avait pas besoin de planifier les types de variables et de réfléchir à la quantité de mémoire nécessaire pour stocker ses données.
Voici ce qui se passe lorsque vous préparez votre code Python pour l'exécution: «En Python, le code source est converti à l'aide de CPython en une forme beaucoup plus simple appelée bytecode. Le bytecode se compose d'instructions qui, en substance, sont similaires aux instructions du processeur. Mais ils ne sont pas exécutés par le processeur, mais par un système logiciel appelé machine virtuelle. (Il ne s'agit pas de machines virtuelles dont les capacités vous permettent d'exécuter des systèmes d'exploitation entiers sur elles. Dans notre cas, il s'agit d'un environnement qui est une version simplifiée de l'environnement disponible pour les programmes s'exécutant sur le processeur). "
Comment CPython sait-il quels types de variables doivent être lorsqu'il prépare un programme pour l'exécution? Après tout, nous n'avons pas indiqué ces types. CPython n'en sait rien. Il sait seulement que les variables sont des objets. Tout en Python est un
objet , au moins jusqu'à ce qu'il s'avère que quelque chose a un type plus spécifique.
Par exemple, Python considère comme une chaîne tout ce qui est entouré de guillemets simples ou doubles. Si Python rencontre un nombre, il considère que la valeur correspondante est de type numérique. Si nous essayons de faire quelque chose avec une entité qui ne peut pas être fait avec une entité de son type, Python nous le fera savoir plus tard.
Considérez le message d'erreur suivant qui s'affiche lorsque vous essayez d'ajouter une chaîne et un nombre:
name = 'Vicki' seconds = 4.71; --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-9-71805d305c0b> in <module> 3 4 ----> 5 name + seconds TypeError: must be str, not float
Le système nous dit qu'il ne peut pas ajouter de chaînes et de nombres à virgule flottante. De plus, le fait que le
name
soit une chaîne et les
seconds
un nombre n'intéressait pas le système jusqu'à ce qu'une tentative soit faite pour ajouter le
name
et les
seconds
.
En d'autres termes, il peut être décrit
comme suit : «Le typage canard est utilisé lors de l'addition. Python n'est pas intéressé par le type d'un objet particulier. Tout ce qui intéresse le système, c'est s'il renvoie un appel significatif à la méthode d'addition. Si ce n'est pas le cas, une erreur est générée. "
Qu'est-ce que cela signifierait? Cela signifie que si nous écrivons des programmes Python, nous ne recevrons pas de message d'erreur tant que l'interpréteur CPython ne sera pas engagé dans l'exécution de la même ligne dans laquelle il y a une erreur.
Cette approche n'était pas pratique lorsqu'elle était appliquée dans des équipes travaillant sur de grands projets. Le fait est que dans de tels projets, ils ne fonctionnent pas avec des variables distinctes, mais avec des structures de données complexes. Dans de tels projets, certaines fonctions sont appelées par d'autres, et celles-ci, à leur tour, sont appelées par d'autres fonctions. Les membres de l'équipe doivent pouvoir vérifier rapidement le code de leurs projets. S'ils ne sont pas en mesure d'écrire de bons tests qui détectent les erreurs dans les projets avant leur mise en production, cela signifie que ces projets peuvent s'attendre à de gros problèmes.
À proprement parler, nous arrivons ici à la conversation sur les annotations de type en Python.
On peut dire qu'en général, l'utilisation d'annotations de type a de nombreux
atouts . Si vous travaillez avec des structures de données ou des fonctions complexes qui prennent de nombreuses valeurs d'entrée, l'utilisation d'annotations simplifie considérablement le travail avec des structures et des fonctions similaires. Surtout - quelque temps après leur création. Si vous n'avez qu'une seule fonction avec un seul paramètre, comme dans les exemples donnés ici, alors travailler avec une telle fonction est, en tout cas, très simple.
Et si nous devons travailler avec des fonctions complexes qui prennent de nombreuses valeurs d'entrée similaires à celles de la documentation
PyTorch :
def train(args, model, device, train_loader, optimizer, epoch): model.train() for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() optimizer.step() if batch_idx % args.log_interval == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item()))
Qu'est-ce que le
model
? Bien sûr, nous pouvons creuser dans la base de code et découvrir:
model = Net().to(device)
Mais ce serait bien si vous pouviez simplement spécifier le type de
model
dans la signature de fonction et vous épargner des analyses de code inutiles. Peut-être que cela ressemblerait à ceci:
def train(args, model (type Net), device, train_loader, optimizer, epoch):
Et l'
device
? Si vous fouillez le code, vous pouvez trouver les informations suivantes:
device = torch.device("cuda" if use_cuda else "cpu")
Nous sommes maintenant confrontés à la question de savoir ce qu'est un appareil
torch.device
. Il s'agit d'un type spécial de PyTorch. Sa description se trouve dans la
section correspondante de la documentation PyTorch.
Ce serait bien si nous pouvions spécifier le type
device
dans la liste d'arguments de la fonction. Ainsi, nous gagnerions beaucoup de temps à ceux qui devraient analyser ce code.
def train(args, model (type Net), device (type torch.Device), train_loader, optimizer, epoch):
Ces considérations peuvent perdurer très longtemps.
En conséquence, il s'avère que les annotations de type sont très utiles pour quelqu'un qui écrit du code. Mais ils profitent également à ceux qui lisent le code des autres. Il est beaucoup plus facile de lire du code dactylographié que du code, pour comprendre que vous devez gérer ce qu'est une entité. Les annotations de type améliorent la lisibilité du code.
Alors, qu'est-ce qui a été fait en Python pour amener le code au même niveau de lisibilité qui distingue le code écrit dans des langages typés statiquement?
Annotations de type en Python
Nous sommes maintenant prêts à parler sérieusement des annotations de type en Python. Lors de la lecture de programmes écrits en Python 2, on pouvait voir que les programmeurs fournissaient leur code avec des conseils indiquant aux lecteurs du code de quel type sont les variables ou les valeurs renvoyées par les fonctions.
Un code similaire ressemblait à l'origine à
ceci :
users = []
Les annotations de type étaient auparavant de simples commentaires. Mais il se trouve que Python a commencé à évoluer progressivement vers une manière plus uniforme de gérer les annotations. En particulier, nous parlons de l'émergence du document
PEP 3107 , dédié à l'annotation des fonctions.
Ensuite, les travaux ont commencé sur le
PEP 484 . Ce document, consacré aux annotations de types, a été développé en étroite relation avec mypy, le projet DropBox, qui vise à vérifier les types avant d'exécuter des scripts. En utilisant mypy, il convient de se rappeler que la vérification de type n'est pas effectuée pendant l'exécution du script. Un message d'erreur peut être reçu lors de l'exécution si, par exemple, vous essayez de créer quelque chose d'un type que ce type ne prend pas en charge. Dites - si vous essayez de découper un dictionnaire ou appelez la méthode
.pop()
pour une chaîne.
Voici ce que vous pouvez apprendre du PEP 484 sur les détails de la mise en œuvre des annotations: «Bien que ces annotations soient disponibles au moment de l'exécution via l'attribut d'
annotations
standard, aucune vérification de type n'est effectuée au moment de l'exécution. Au lieu de cela, cette proposition prévoit l'existence d'un outil de vérification de type autonome distinct, avec lequel l'utilisateur, s'il le souhaite, peut vérifier le code source de ses programmes. En général, un outil de vérification de type similaire fonctionne comme un linter très puissant. "Bien que, bien sûr, les utilisateurs individuels puissent utiliser un outil similaire pour vérifier les types au moment de l'exécution, soit pour la mise en œuvre de la méthodologie Design By Contract ou pour la mise en œuvre de l'optimisation JIT. Mais il convient de noter que ces outils n'ont pas encore atteint une maturité suffisante."
À quoi ressemble le travail avec les annotations de type dans la pratique?
Par exemple, leur utilisation signifie la capacité de faciliter le travail dans divers IDE. Ainsi,
PyCharm propose, en fonction des informations de type, de l'achèvement du code et de la vérification. Des fonctionnalités similaires sont disponibles dans VS Code.
Les annotations de type sont utiles pour une raison supplémentaire: elles protègent le développeur contre les erreurs stupides.
Voici un excellent exemple d'une telle protection.
Supposons que nous ajoutions les noms des personnes au dictionnaire:
names = {'Vicki': 'Boykis', 'Kim': 'Kardashian'} def append_name(dict, first_name, last_name): dict[first_name] = last_name append_name(names,'Kanye',9)
Si nous l'autorisons, il y aura de nombreuses entrées incorrectement formées dans le dictionnaire.
Corrigeons ceci:
from typing import Dict names_new: Dict[str, str] = {'Vicki': 'Boykis', 'Kim': 'Kardashian'} def append_name(dic: Dict[str, str] , first_name: str, last_name: str): dic[first_name] = last_name append_name(names_new,'Kanye',9.7) names_new
Maintenant, vérifiez ce code avec mypy et obtenez ce qui suit:
(kanye) mbp-vboykis:types vboykis$ mypy kanye.py kanye.py:9: error: Argument 3 to "append_name" has incompatible type "float"; expected "str"
On peut voir que mypy ne permet pas l'utilisation de nombres là où la chaîne est attendue. Ceux qui souhaitent utiliser régulièrement ces tests sont invités à inclure mypy lorsque les tests de code sont effectués dans leurs systèmes d'intégration continue.
Tapez des astuces dans divers IDE
L'un des avantages les plus importants de l'utilisation des annotations de type est qu'ils permettent aux programmeurs Python d'utiliser les mêmes fonctionnalités de complétion de code dans divers IDE disponibles pour les langages typés statiquement.
Par exemple, supposons que vous ayez un morceau de code semblable au suivant. Il s'agit de quelques fonctions des exemples précédents encapsulées dans des classes.
from typing import Dict class rainfallRate: def __init__(self, hours, inches): self.hours= hours self.inches = inches def calculateRate(self, inches:int, hours:int) -> float: return inches/hours rainfallRate.calculateRate() class addNametoDict: def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name self.dict = dict def append_name(dict:Dict[str, str], first_name:str, last_name:str): dict[first_name] = last_name addNametoDict.append_name()
La bonne chose est que nous, de notre propre initiative, avons ajouté des descriptions de types au code, nous pouvons observer ce qui se passe dans le programme lorsque les méthodes de classe sont appelées:
Conseils de type IDEPremiers pas avec les annotations de type
Vous pouvez trouver de bonnes recommandations dans la documentation de mypy pour savoir par quoi commencer lorsque vous commencez à taper la base de code:
- Commencez petit - assurez-vous que certains fichiers contenant plusieurs annotations sont validés avec mypy.
- Écrivez un script pour exécuter mypy. Cela aidera à obtenir des résultats de test cohérents.
- Exécutez mypy dans les pipelines CI pour éviter les erreurs de type.
- Annotez progressivement les modules les plus utilisés dans le projet.
- Ajoutez des annotations de type au code existant que vous modifiez; équipez-les du nouveau code que vous écrivez.
- Utilisez MonkeyType ou PyAnnotate pour annoter automatiquement l'ancien code.
Avant de vous lancer dans l'annotation de votre propre code, il vous sera utile de traiter quelque chose.
Tout d'abord, vous devrez importer le module de
saisie dans le code si vous utilisez autre chose que des chaînes, des entiers, des booléens et les valeurs d'autres types de base Python.
Deuxièmement, ce module permet de travailler avec plusieurs types complexes. Parmi eux,
Dict
,
Tuple
,
List
et
Set
. La construction du formulaire
Dict[str, float]
signifie que vous souhaitez travailler avec un dictionnaire dont les éléments utilisent une chaîne comme clé et un nombre à virgule flottante comme valeur. Il existe également des types appelés
Optional
et
Union
.
Troisièmement, vous devez vous familiariser avec le format des annotations de type:
import typing def some_function(variable: type) -> return_type: do_something
Si vous souhaitez en savoir plus sur la façon de commencer à appliquer des annotations de type dans vos projets, je voudrais noter que de nombreux bons didacticiels y sont consacrés.
En voici un. Je le considère comme le meilleur. Après l'avoir maîtrisé, vous en apprendrez plus sur l'annotation de code et sa vérification.
Les résultats. Vaut-il la peine d'utiliser des annotations de type en Python?
Maintenant, demandons-nous si vous devez utiliser des annotations de type en Python. En fait, cela dépend des caractéristiques de votre projet. Voici ce que Guido van Rossum dit à ce sujet dans la documentation de mypy: «Le but de mypy n'est pas de convaincre tout le monde d'écrire du code Python typé statiquement. La saisie statique est complètement facultative maintenant et à l'avenir. L'objectif de Mypy est d'offrir aux programmeurs Python plus d'options. Il s'agit de faire de Python une alternative plus compétitive aux autres langages typés statiquement utilisés dans les grands projets. Il s'agit d'augmenter la productivité des programmeurs et d'améliorer la qualité des logiciels. "
Le temps nécessaire pour configurer mypy et planifier les types nécessaires à un certain programme ne se justifie pas dans les petits projets et lors des expérimentations (par exemple, celles réalisées dans Jupyter). Quel projet devrait être considéré comme petit? Probablement celui dont le volume, selon des estimations soigneuses, ne dépasse pas 1000 lignes.
Les annotations de type ont un sens dans les grands projets. Là, ils peuvent notamment gagner beaucoup de temps. Nous parlons de projets développés par des groupes de programmeurs, de packages, de code, qui sont utilisés dans le développement de systèmes de contrôle de version et de pipelines CI.
Je crois que les annotations de type au cours des deux prochaines années deviendront beaucoup plus courantes que maintenant, sans parler du fait qu'elles pourraient bien devenir un outil ordinaire de tous les jours. Et je crois que quelqu'un qui commence à travailler avec eux avant les autres ne perdra rien.
Chers lecteurs! Utilisez-vous des annotations de type dans vos projets Python?
