Module de typage Python - Utiliser efficacement les vérificateurs de type
Introduit depuis Python 3.5, le module typing de Python tente de fournir un moyen de suggérer des types afin d'aider les vérificateurs de types statiques et les analyseurs à prédire avec précision les erreurs.
En raison du fait que Python doit déterminer le type des objets pendant l'exécution, il est parfois très difficile pour les développeurs de comprendre ce qui se passe exactement dans le code.
Même les vérificateurs de type externes comme PyCharm IDE ne produisent pas les meilleurs résultats ; en moyenne, ils prédisent correctement les erreurs environ 50 % du temps, selon cette réponse sur StackOverflow.
Python tente de réduire ce problème en introduisant ce qui est connu sous le nom de hinting de type (annotation de type) pour aider les vérificateurs de type externes à identifier les éventuelles erreurs. C'est un bon moyen pour le programmeur d'indiquer le type des objets utilisés, dès le temps de compilation, et de s'assurer que les vérificateurs de type fonctionnent correctement.
Cela rend le code Python beaucoup plus lisible et robuste pour les autres lecteurs !
REMARQUE : Cela ne fait pas de vérification de type réelle à la compilation. Si l'objet réel renvoyé n'était pas du même type que celui suggéré, il n'y aura aucune erreur de compilation. C'est pourquoi nous utilisons des vérificateurs de type externes, comme mypy, pour identifier les erreurs de type.
Conditions préalables recommandées
Pour utiliser efficacement le module typing, il est recommandé d'utiliser un vérificateur de type/linter externe pour vérifier la correspondance des types statiques. L'un des vérificateurs de type les plus largement utilisés pour Python est mypy, donc je vous recommande de l'installer avant de lire le reste de l'article.
Nous avons déjà couvert les bases de la vérification de type en Python. Vous pouvez d'abord lire cet article.
Nous utiliserons mypy comme vérificateur de type statique dans cet article, qui peut être installé en:
pip3 install mypy
Vous pouvez exécuter mypy sur n'importe quel fichier Python pour vérifier si les types correspondent. C'est comme si vous 'compiliez' du code Python.
mypy program.py
Après avoir débogué les erreurs, vous pouvez exécuter le programme normalement en utilisant :
python program.py
Maintenant que nous avons couvert nos prérequis, essayons d'utiliser certaines des fonctionnalités du module.
Indices de type/Annotations de type
Sur les fonctions
Nous pouvons annoter une fonction pour spécifier son type de retour et les types de ses paramètres.
def print_list(a: list) -> None:
print(a)
Cela informe le vérificateur de type (mypy dans mon cas) que nous avons une fonction print_list(), qui prendra une list en argument et retournera None.
def print_list(a: list) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
Exécutons cela d'abord sur notre vérificateur de type mypy :
vijay@JournalDev:~ $ mypy printlist.py
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)
Comme prévu, nous obtenons une erreur ; car la ligne #5 a l'argument en tant que int, plutôt qu'en tant que liste.
Sur les variables
Depuis Python 3.6, nous pouvons également annoter les types de variables, en mentionnant le type. Mais ce n'est pas obligatoire si vous souhaitez que le type d'une variable change avant que la fonction ne retourne.
Annotates 'radius' to be a float
radius: float = 1.5
We can annotate a variable without assigning a value!
sample: int
Annotates 'area' to return a float
def area(r: float) -> float:
return 3.1415 * r * r
print(area(radius))
Print all annotations of the function using
the '__annotations__' dictionary
print('Dictionary of Annotations for area():', area.__annotations__)
Sortie de mypy:
vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}
C'est la manière recommandée d'utiliser mypy, en fournissant d'abord des annotations de type, avant d'utiliser le vérificateur de type.
Alias de type
Le module typing nous fournit des alias de type, qui sont définis en attribuant un type à l'alias.
from typing import List
Vector is a list of float values
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)
Sortie
vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]
Dans l'extrait ci-dessus, Vector est un alias, qui représente une liste de valeurs à virgule flottante. Nous pouvons indiquer un type pour un alias, ce que fait le programme ci-dessus.
La liste complète des alias acceptables est donnée ici.
Regardons un exemple de plus, qui vérifie chaque paire clé:valeur dans un dictionnaire et vérifie si elle correspond au format nom:email.
from typing import Dict
import re
Create an alias called 'ContactDict'
ContactDict = Dict[str, str]
def check_if_valid(contacts: ContactDict) -> bool:
for name, email in contacts.items():
# Check if name and email are strings
if (not isinstance(name, str)) or (not isinstance(email, str)):
return False
# Check for email xxx@yyy.zzz
if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
return False
return True
print(check_if_valid({'vijay': 'vijay@sample.com'}))
print(check_if_valid({'vijay': 'vijay@sample.com', 123: 'wrong@name.com'}))
Sortie de mypy
vijay@JournalDev:~ $ mypy validcontacts.py
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)
Ici, nous obtenons une erreur de compilation statique dans mypy, puisque le paramètre name dans notre deuxième dictionnaire est un entier (123). Ainsi, les alias sont une autre façon d'imposer un contrôle de type précis par mypy.
Créez des types de données définis par l'utilisateur en utilisant NewType()
Nous pouvons utiliser la fonction NewType() pour créer de nouveaux types définis par l'utilisateur.
from typing import NewType
Create a new user type called 'StudentID' that consists of
an integer
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)
Le vérificateur de type statique traitera le nouveau type comme s'il était une sous-classe du type original. Cela est utile pour aider à détecter les erreurs logiques.
from typing import NewType
Create a new user type called 'StudentID'
StudentID = NewType('StudentID', int)
def get_student_name(stud_id: StudentID) -> str:
return str(input(f'Enter username for ID #{stud_id}:\n'))
stud_a = get_student_name(StudentID(100))
print(stud_a)
This is incorrect!!
stud_b = get_student_name(-1)
print(stud_b)
Sortie de mypy
vijay@JournalDev:~ $ mypy studentnames.py
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)
Le tout type
C'est un type spécial, informant le vérificateur de type statique (mypy dans mon cas) que chaque type est compatible avec ce mot-clé.
Considérez notre ancienne fonction print_list(), maintenant acceptant des arguments de tout type.
from typing import Any
def print_list(a: Any) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
Maintenant, il n'y aura plus d'erreurs lorsque nous exécuterons mypy.
vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1
Toutes les fonctions sans type de retour ou types de paramètres utiliseront implicitement Any par défaut.
def foo(bar):
return bar
A static type checker will treat the above
as having the same signature as:
def foo(bar: Any) -> Any:
return bar
Vous pouvez donc utiliser Any pour mélanger le code typé statiquement et dynamiquement.
Conclusion
Dans cet article, nous avons appris à propos du module typing de Python, qui est très utile dans le contexte de la vérification de type, permettant à des vérificateurs de type externes comme mypy de signaler avec précision les erreurs.
Ceci nous offre un moyen d'écrire du code à typage statique en Python, qui est par conception un langage à typage dynamique !
Références
- Documentation Python pour le module typing (Cela contient des détails étendus sur plus de méthodes dans ce module, et je recommande cela comme référence secondaire)
- Question sur StackOverflow concernant les indices de type (Cela fournit une très bonne discussion sur le sujet. Je vous recommande vivement de lire ce sujet également !)
