littéralement: suggestions de typage
motivations¶
le fait de pouvoir écrire du code non typé est pratique (rapidité de développement)
mais a des limitations (notamment: confusion possible sur l’utilisation des librairies)
le but avec les type hints est introduire un mécanisme optionnel pour améliorer la situation
meilleure documentation: faciliter l’accès à une nouvelle lib
analyse statique: trouver les bugs plus tôt
et plus anecdotiquement, meilleures performances
complètement optionnel¶
non seulement on n’est pas obligé d’en mettre
mais même si on en met, c’est ignoré ! à run-time
# les annotations ici indiquent que
# les paramètres et le retour sont des entiers
def ajouter(x: int, y:int) -> int:
return x + y# mais à run-time celles-ci sont ignorées !
# la preuve, je peux utiliser des chaines à la place
ajouter('abc', 'def')'abcdef'à quoi ça sert alors ? vérifier¶
vérification statique en utilisant mypy - un outil externe
# l'analyse statique est faite par un outil externe
# ici je choisis d'utiliser mypy
%pip install mypyDefaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: mypy in /home/runner/.local/lib/python3.12/site-packages (1.18.2)
Requirement already satisfied: typing_extensions>=4.6.0 in /usr/lib/python3/dist-packages (from mypy) (4.10.0)
Requirement already satisfied: mypy_extensions>=1.0.0 in /home/runner/.local/lib/python3.12/site-packages (from mypy) (1.1.0)
Requirement already satisfied: pathspec>=0.9.0 in /home/runner/.local/lib/python3.12/site-packages (from mypy) (0.12.1)
Note: you may need to restart the kernel to use updated packages.
du coup avec ce fichier source
1 2 3 4 5 6 7 8# on ajoute des annotations à l'objet 'ajouter' def ajouter(x: int, y: int) -> int: return x + y # mais à run-time celles-ci sont ignorées ! ajouter('abc', 'def')
dans le dossier samples/
on obtient ces diagnostics d’erreur sans avoir besoin d’exécuter
ce qui signifie qu’on peut le faire AVANT même de faire les tests
!mypy samples/typehints.pysamples/typehints.py:8: error: Argument 1 to "ajouter" has incompatible type "str"; expected "int" [arg-type]
samples/typehints.py:8: error: Argument 2 to "ajouter" has incompatible type "str"; expected "int" [arg-type]
Found 2 errors in 1 file (checked 1 source file)
à quoi ça sert alors ? documenter¶
meilleure documentation !
on perd moins de temps perdu à deviner les présupposés sur les arguments
def ajouter(x: int, y:int) -> int:
"""
add 2 integers
"""
return x + y
help(ajouter);Help on function ajouter in module __main__:
ajouter(x: int, y: int) -> int
add 2 integers
les variables aussi¶
au départ on pense surtout à typer les paramètres, et la sortie des fonctions
mais on peut aussi typer les variables
# le type hint permet aussi de préciser les choses sur les variables
NAMES : list[str] = []comment définir un type¶
les classes builtin¶
dans les cas simples on utilise directement les classes builtin str, dict etc..
notez qu’on peut utiliser les [] pour fabriquer des types plus précis, par exemple
| liste d’enters |
| un tuple à deux éléments, une chaine et un entier |
| dictionnaire dont les clés sont des chaines et les valeurs des listes d’entiers |
aliases¶
évidemment on utilise souvent les mêmes types dans une application;
aussi on peut se définir son type perso avec une simple affectation, comme par exemple
# on définit un alias de type avec une simple affectation
Name = str
Age = int
Phone = str
# ce type décrit les tuples qui contiennent deux chaines et un entier
Employee = tuple[Name, Age, Phone]
# et donc ensuite on peut utiliser Employee dans un type hintclasses¶
pour quasiment le même genre d’usage, on peut bien sûr utiliser aussi les noms de classe
class Foo:
pass
def link_foos(foo1: Foo, foo2: Foo) -> None:
foo1.links.add(foo2)le module typing (1)¶
le module typing permet d’étendre le spectre, en introduisant des concepts additionnels
comme par exemple Sequence, Iterable, Callable
voici quelques exemples
from typing import Iterable, Iterator, Sequence, Callable
# (le type qui décrit) n'importe quel itérable qui contient des entiers
Iterable[int]
# pareil pour un itérateur
Iterator[int]
# ou une séquence
Sequence[int]
# pour le callable la syntaxe est un peu moins triviale:
# ici une fonction qui prend deux paramètres de types resp. Foo et Bar
# et qui retourne un booléen
f: Callable[[Foo, Bar], bool]pour les détails, reportez-vous comme d’hab à https://
le module typing (2)¶
on y trouve aussi d’autres constructeurs qui peuvent être utiles, en vrac:
Anypeut être n’importe quoiUnionlorsqu’on accepte plusieurs typesOptionalpour une variable qui peut aussi êtreNoneHashablece qui peut être utilisé comme clé d’un dictionnaire
et aussi
NewTypedéfinit un nom pour un type - un peu plus subtil que l’affectationTypeVarpour manipuler des types génériques (à la template C++)grâce auxquels on peut implémenter des classes génériques
conclusion¶
comme il s’agit d’un trait relativement récent (en fait ça n’est stable que depuis la 3.9),
les type hints ne sont pas massivment présents dans le code existant
toutefois c’est clairement une voie à suivre, et en tous cas c’est important que vous sachiez le lire !
souvenez-vous qu’on peut les utiliser ponctuellement
du coup pensez à l’utiliser à chaque fois que les choses deviennent ambigües
par contre, pour pouvoir faire de la vérification statique, il faut viser une couverture beaucoup plus large pour que ça apporte vraiment quelque chose
ATTENTION avec isinstance¶
(avancé)
on serait tenté d’utiliser isinstance/issubclass avec les types tels qu’on vient de les définir
mais il ne faut pas le faire! je vous renvoie à ce post
il y est suggéré de disposer d’une autre builtin que isinstance
pour vérifier si une variable est acceptable pour un type
alors que isinstance se base uniquement sur l’héritage de classes
# ceci déclenche un TypeError
from typing import Union
Phone = Union[str, int]
PhoneSet = set[Phone]
try:
isinstance( {'0123456789', 98765432}, PhoneSet)
except Exception as exc:
print(f"{type(exc)} {exc}")<class 'TypeError'> isinstance() argument 2 cannot be a parameterized generic