Contactez-nous

Introduction aux annotations de type

Découvrez les annotations de type en Python (type hints), introduites en Python 3.5. Améliorez la lisibilité et la maintenabilité de votre code en indiquant les types des variables, des arguments de fonctions et des valeurs de retour. Utilisez mypy pour l

Qu'est-ce que le typage en Python ? Dynamique et fort

Python est un langage à typage *dynamique* et *fort*.

  • Typage dynamique : Le type d'une variable n'est pas déclaré explicitement. Il est déterminé au moment de l'exécution, en fonction de la valeur affectée à la variable. Une même variable peut changer de type au cours de l'exécution du programme.
  • Typage fort : Une fois qu'une variable a un type, Python n'effectue pas de conversions de type implicites qui pourraient entraîner une perte d'information ou un comportement inattendu. Par exemple, vous ne pouvez pas additionner directement un entier et une chaîne de caractères.

Exemple :

x = 10      # x est un entier
x = "abc"   # Maintenant, x est une chaîne de caractères (typage dynamique)

# x + 5    # Lèverait une TypeError (typage fort)

Le typage dynamique offre une grande flexibilité, mais il peut aussi rendre le code plus difficile à comprendre et à déboguer, car le type d'une variable n'est pas toujours évident.

Annotations de type (type hints) : indiquer les types attendus

Les annotations de type (type hints) ont été introduites en Python 3.5 (PEP 484) pour permettre d'indiquer les types attendus pour les variables, les arguments de fonctions, et les valeurs de retour.

Les annotations de type sont *facultatives*. Elles n'affectent pas le comportement du programme à l'exécution (l'interpréteur Python les ignore).

Elles servent principalement à :

  • Améliorer la lisibilité du code : Les annotations de type rendent le code plus facile à comprendre, car elles indiquent explicitement les types attendus.
  • Faciliter le débogage : Les annotations de type peuvent aider à identifier les erreurs de type plus tôt, avant l'exécution du programme.
  • Permettre la vérification statique des types : Des outils externes (comme `mypy`) peuvent utiliser les annotations de type pour vérifier statiquement (sans exécuter le code) que les types sont corrects.
  • Améliorer l'autocomplétion et l'aide contextuelle dans les IDE.

Les annotations de type sont une forme de documentation "exécutable" (car elles peuvent être vérifiées par des outils).

Syntaxe des annotations de type

La syntaxe des annotations de type est la suivante :

  • Variables : `nom_variable: type = valeur`
  • Arguments de fonction : `def fonction(arg1: type1, arg2: type2 = valeur_par_defaut) -> type_de_retour:`
  • Valeur de retour de fonction : `def fonction(...) -> type_de_retour:`

Exemples :

# Variables
x: int = 10
y: str = "Bonjour"
liste: list[int] = [1, 2, 3]

# Fonction
def additionner(a: int, b: int) -> int:
    return a + b

# Classe
class MaClasse:
    attribut: str

    def methode(self, arg: float) -> bool:
      # ...
      return True

Dans cet exemple:

  • `x: int = 10` indique que la variable `x` est censée contenir un entier.
  • `y: str = "Bonjour"` indique que la variable `y` est censée contenir une chaîne de caractères.
  • `liste: list[int] = [1, 2, 3]` indique que la variable `liste` est censée contenir une liste d'entiers.
  • `def additionner(a: int, b: int) -> int:` indique que la fonction `additionner` prend deux arguments entiers (`a` et `b`) et retourne un entier.
  • `def methode(self, arg: float) -> bool:` indique que la méthode `methode` prend un argument flottant et retourne un booléen.

Types de base et types composés

Vous pouvez utiliser les types de base de Python pour les annotations de type :

  • `int` : Entiers
  • `float` : Nombres à virgule flottante
  • `str` : Chaînes de caractères
  • `bool` : Booléens (`True` ou `False`)
  • `bytes` : Séquences d'octets
  • `None` : Le type de `None` (souvent utilisé pour indiquer qu'une fonction ne retourne rien)

Pour les types composés (listes, tuples, dictionnaires, ensembles, etc.), vous pouvez utiliser :

  • `list[type]` : Liste d'éléments de type `type`.
  • `tuple[type1, type2, ...]` : Tuple contenant des éléments de type `type1`, `type2`, etc.
  • `dict[cle_type, valeur_type]` : Dictionnaire avec des clés de type `cle_type` et des valeurs de type `valeur_type`.
  • `set[type]` : Ensemble d'éléments de type `type`.

Exemples :

liste_entiers: list[int] = [1, 2, 3]
tuple_mixte: tuple[int, str, bool] = (1, "a", True)
dictionnaire: dict[str, int] = {"a": 1, "b": 2}
ensemble: set[float] = {1.0, 2.5, 3.14}

Le module `typing` (voir section suivante) fournit des types plus avancés (comme `Optional`, `Union`, `Callable`, `Any`, etc.) pour exprimer des contraintes de type plus complexes.

Le module typing : types plus avancés

Le module `typing` de la bibliothèque standard fournit des types plus avancés pour les annotations de type. Il permet d'exprimer des contraintes de type plus précises et plus complexes.

Voici quelques exemples de types utiles définis dans le module `typing` :

  • `Optional[type]` : Indique qu'une variable ou un argument peut être soit du type `type`, soit `None`. `Optional[int]` est équivalent à `Union[int, None]`.
  • `Union[type1, type2, ...]` : Indique qu'une variable ou un argument peut être de l'un des types spécifiés.
  • `Any` : Indique que la variable ou l'argument peut être de n'importe quel type. Utiliser `Any` revient à ne pas utiliser d'annotation de type.
  • `Callable[[arg1_type, arg2_type, ...], retour_type]` : Indique qu'une variable ou un argument est une fonction (ou un objet appelable) qui prend des arguments de type `arg1_type`, `arg2_type`, etc., et retourne une valeur de type `retour_type`.
  • `Sequence[type]` : Indique une séquence (par exemple, une liste ou un tuple) d'éléments de type `type`.
  • `Mapping[cle_type, valeur_type]` : Indique un mappage (par exemple, un dictionnaire) avec des clés de type `cle_type` et des valeurs de type `valeur_type`.
  • `Iterable[type]` : Indique un itérable dont les éléments sont de type `type`.
  • `TypeVar` : Permet de définir des variables de type (pour le code générique).
  • Et bien d'autres...

Pour utiliser ces types, vous devez importer le module `typing` :

from typing import Optional, Union, List, Dict, Callable

def ma_fonction(x: Optional[int], y: Union[str, float]) -> List[Dict[str, int]]:
    # ...

Dans cet exemple :

  • `x` peut être soit un entier, soit `None`.
  • `y` peut être soit une chaîne de caractères, soit un nombre à virgule flottante.
  • La fonction retourne une liste de dictionnaires, où les clés sont des chaînes et les valeurs sont des entiers.

Vérification statique des types avec mypy

Les annotations de type en Python sont *facultatives* et n'affectent pas le comportement du programme à l'exécution. L'interpréteur Python les ignore.

Cependant, vous pouvez utiliser un outil externe appelé `mypy` pour effectuer une *vérification statique* des types. `mypy` analyse votre code (sans l'exécuter) et vérifie que les types que vous avez annotés sont respectés.

Pour utiliser `mypy`, vous devez l'installer :

pip install mypy

Ensuite, vous pouvez exécuter `mypy` sur votre code :

mypy mon_fichier.py

Si `mypy` détecte des erreurs de type, il affichera des messages d'erreur. Sinon, il ne produira aucune sortie.

Exemple :

# mon_fichier.py
def additionner(a: int, b: int) -> int:
    return a + b

print(additionner(1, 2))
print(additionner("a", "b"))  # Erreur de type détectée par mypy

Si vous exécutez `mypy mon_fichier.py`, vous obtiendrez une erreur du type :

mon_fichier.py:6: error: Argument 1 to "additionner" has incompatible type "str"; expected "int"  [arg-type]

`mypy` vous indique que vous avez appelé la fonction `additionner` avec des arguments de type `str`, alors qu'elle attend des arguments de type `int`.

L'utilisation de `mypy` (ou d'autres outils de vérification statique de type) peut vous aider à détecter les erreurs de type plus tôt, à améliorer la qualité de votre code, et à faciliter la maintenance.

Limitations et considérations

Les annotations de type et `mypy` sont des outils puissants, mais il est important de comprendre leurs limitations :

  • Les annotations de type sont facultatives : Vous n'êtes pas obligé d'annoter tout votre code. Vous pouvez commencer par annoter les parties les plus critiques, puis ajouter progressivement des annotations.
  • `mypy` est un outil externe : Il n'est pas intégré à l'interpréteur Python. Vous devez l'installer et l'exécuter séparément.
  • `mypy` n'est pas parfait : Il peut y avoir des faux positifs (erreurs signalées par `mypy` qui ne sont pas de vraies erreurs) et des faux négatifs (erreurs de type non détectées par `mypy`).
  • Les annotations de type n'affectent pas l'exécution : Python reste un langage à typage dynamique. Les annotations de type sont ignorées par l'interpréteur. Elles ne changent pas le comportement du programme à l'exécution. Elles servent uniquement à la vérification statique (par `mypy` ou d'autres outils).
  • Les annotations de type peuvent être verbeuses : Dans certains cas, les annotations de type peuvent rendre le code plus verbeux et moins lisible. Il faut trouver un équilibre entre la précision des annotations et la lisibilité du code.

Les annotations de type sont un outil précieux pour améliorer la qualité et la maintenabilité de votre code Python, mais elles ne sont pas une solution miracle. Utilisez-les judicieusement, en complément d'autres bonnes pratiques (tests unitaires, documentation, etc.).