Skip to article frontmatterSkip to article content

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

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 mypy
Defaulting 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

typehints.py
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.py
samples/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

list[int]

liste d’enters

tuple[str, int]

un tuple à deux éléments, une chaine et un entier

dict[str, list[int]]

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 hint

classes

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://docs.python.org/3/library/typing.html

le module typing (2)

on y trouve aussi d’autres constructeurs qui peuvent être utiles, en vrac:

et aussi

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