Introduction aux types Python¶
Python prend en charge des «type hints» (également appelées «annotations de type») optionnels.
Ces «type hints» ou annotations sont une syntaxe spéciale qui permet de déclarer le type d'une variable.
En déclarant des types pour vos variables, les éditeurs et les outils peuvent vous offrir un meilleur support.
Il s'agit d'un tutoriel rapide / rappel sur les «type hints» Python. Il ne couvre que le minimum nécessaire pour les utiliser avec FastAPI ... ce qui est en réalité très peu.
FastAPI est entièrement basé sur ces «type hints», qui lui apportent de nombreux avantages et bénéfices.
Mais même si vous n'utilisez jamais FastAPI, vous auriez intérêt à en apprendre un peu à leur sujet.
Remarque
Si vous êtes un expert Python et que vous savez déjà tout sur les «type hints», passez au chapitre suivant.
Motivation¶
Commençons par un exemple simple :
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Exécuter ce programme affiche :
John Doe
La fonction fait ce qui suit :
- Prend un
first_nameet unlast_name. - Convertit la première lettre de chacun en majuscule avec
title(). - Concatène les deux avec un espace au milieu.
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Modifiez-le¶
C'est un programme très simple.
Mais maintenant, imaginez que vous l'écriviez à partir de zéro.
À un certain moment, vous auriez commencé la définition de la fonction, vous aviez les paramètres prêts...
Mais ensuite, vous devez appeler «cette méthode qui convertit la première lettre en majuscule».
Était-ce upper ? Était-ce uppercase ? first_uppercase ? capitalize ?
Alors, vous essayez l'ami de toujours du programmeur : l'autocomplétion de l'éditeur.
Vous saisissez le premier paramètre de la fonction, first_name, puis un point (.) et appuyez sur Ctrl+Espace pour déclencher l'autocomplétion.
Mais, malheureusement, vous n'obtenez rien d'utile :

Ajouter des types¶
Modifions une seule ligne par rapport à la version précédente.
Nous allons changer exactement ce fragment, les paramètres de la fonction, de :
first_name, last_name
à :
first_name: str, last_name: str
C'est tout.
Ce sont les «type hints» :
def get_full_name(first_name: str, last_name: str):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Ce n'est pas la même chose que de déclarer des valeurs par défaut comme on le ferait avec :
first_name="john", last_name="doe"
C'est différent.
Nous utilisons des deux-points (:), pas des signes égal (=).
Et ajouter des «type hints» ne change normalement pas ce qui se passe par rapport à ce qui se passerait sans eux.
Mais maintenant, imaginez que vous êtes à nouveau en train de créer cette fonction, mais avec des «type hints».
Au même moment, vous essayez de déclencher l'autocomplétion avec Ctrl+Espace et vous voyez :

Avec cela, vous pouvez faire défiler, voir les options, jusqu'à trouver celle qui «vous dit quelque chose» :

Plus de motivations¶
Regardez cette fonction, elle a déjà des «type hints» :
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + age
return name_with_age
Comme l'éditeur connaît les types des variables, vous n'obtenez pas seulement l'autocomplétion, vous bénéficiez également de vérifications d'erreurs :

Maintenant vous savez qu'il faut corriger, convertir age en chaîne avec str(age) :
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + str(age)
return name_with_age
Déclarer des types¶
Vous venez de voir l'endroit principal où déclarer des «type hints» : en tant que paramètres de fonctions.
C'est aussi l'endroit principal où vous les utiliserez avec FastAPI.
Types simples¶
Vous pouvez déclarer tous les types standards de Python, pas seulement str.
Vous pouvez utiliser, par exemple :
intfloatboolbytes
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_e
Types génériques avec paramètres de type¶
Il existe des structures de données qui peuvent contenir d'autres valeurs, comme dict, list, set et tuple. Et les valeurs internes peuvent, elles aussi, avoir leur propre type.
Ces types qui ont des types internes sont appelés types «génériques». Et il est possible de les déclarer, même avec leurs types internes.
Pour déclarer ces types et leurs types internes, vous pouvez utiliser le module standard Python typing. Il existe précisément pour prendre en charge ces «type hints».
Versions plus récentes de Python¶
La syntaxe utilisant typing est compatible avec toutes les versions, de Python 3.6 aux plus récentes, y compris Python 3.9, Python 3.10, etc.
Au fur et à mesure que Python évolue, les versions plus récentes offrent un meilleur support pour ces annotations de type et, dans de nombreux cas, vous n'aurez même pas besoin d'importer et d'utiliser le module typing pour déclarer les annotations de type.
Si vous pouvez choisir une version plus récente de Python pour votre projet, vous pourrez profiter de cette simplicité supplémentaire.
Dans toute la documentation, il y a des exemples compatibles avec chaque version de Python (lorsqu'il y a une différence).
Par exemple, «Python 3.6+» signifie que c'est compatible avec Python 3.6 ou supérieur (y compris 3.7, 3.8, 3.9, 3.10, etc.). Et «Python 3.9+» signifie que c'est compatible avec Python 3.9 ou supérieur (y compris 3.10, etc.).
Si vous pouvez utiliser les dernières versions de Python, utilisez les exemples de la dernière version, ils auront la meilleure et la plus simple syntaxe, par exemple, «Python 3.10+».
List¶
Par exemple, définissons une variable comme étant une list de str.
Déclarez la variable, en utilisant la même syntaxe avec les deux-points (:).
Comme type, mettez list.
Comme la liste est un type qui contient des types internes, mettez-les entre crochets :
def process_items(items: list[str]):
for item in items:
print(item)
Info
Ces types internes entre crochets sont appelés «paramètres de type».
Dans ce cas, str est le paramètre de type passé à list.
Cela signifie : «la variable items est une list, et chacun des éléments de cette liste est un str».
En faisant cela, votre éditeur peut vous offrir du support même pendant le traitement des éléments de la liste :

Sans types, c'est presque impossible à atteindre.
Notez que la variable item est l'un des éléments de la liste items.
Et pourtant, l'éditeur sait que c'est un str et fournit le support correspondant.
Tuple et Set¶
Vous feriez la même chose pour déclarer des tuple et des set :
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
return items_t, items_s
Cela signifie :
- La variable
items_test untupleavec 3 éléments, unint, un autreint, et unstr. - La variable
items_sest unset, et chacun de ses éléments est de typebytes.
Dict¶
Pour définir un dict, vous passez 2 paramètres de type, séparés par des virgules.
Le premier paramètre de type est pour les clés du dict.
Le deuxième paramètre de type est pour les valeurs du dict :
def process_items(prices: dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
Cela signifie :
- La variable
pricesest undict:- Les clés de ce
dictsont de typestr(disons, le nom de chaque article). - Les valeurs de ce
dictsont de typefloat(disons, le prix de chaque article).
- Les clés de ce
Union¶
Vous pouvez déclarer qu'une variable peut être n'importe lequel de plusieurs types, par exemple, un int ou un str.
En Python 3.6 et supérieur (y compris Python 3.10), vous pouvez utiliser le type Union de typing et mettre entre crochets les types possibles à accepter.
En Python 3.10, il existe également une nouvelle syntaxe dans laquelle vous pouvez indiquer les types possibles séparés par une barre verticale (|).
def process_item(item: int | str):
print(item)
from typing import Union
def process_item(item: Union[int, str]):
print(item)
Dans les deux cas, cela signifie que item peut être un int ou un str.
Éventuellement None¶
Vous pouvez déclarer qu'une valeur peut avoir un type, comme str, mais qu'elle peut aussi être None.
En Python 3.6 et supérieur (y compris Python 3.10), vous pouvez le déclarer en important et en utilisant Optional depuis le module typing.
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
Utiliser Optional[str] au lieu de simplement str permettra à l'éditeur de vous aider à détecter les erreurs où vous pourriez supposer qu'une valeur est toujours un str, alors qu'elle pourrait en fait être None.
Optional[Something] est en réalité un raccourci pour Union[Something, None], c'est équivalent.
Cela signifie aussi qu'en Python 3.10, vous pouvez utiliser Something | None :
def say_hi(name: str | None = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Union
def say_hi(name: Union[str, None] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
Utiliser Union ou Optional¶
Si vous utilisez une version de Python inférieure à 3.10, voici un conseil de mon point de vue très subjectif :
- 🚨 Évitez d'utiliser
Optional[SomeType] - À la place, ✨ utilisez
Union[SomeType, None]✨.
Les deux sont équivalents et, en interne, c'est la même chose, mais je recommanderais Union plutôt que Optional parce que le mot «optional» semble impliquer que la valeur est optionnelle, alors qu'il signifie en réalité «elle peut être None», même si elle n'est pas optionnelle et reste requise.
Je pense que Union[SomeType, None] est plus explicite quant à ce que cela signifie.
Il ne s'agit que de mots et de noms. Mais ces mots peuvent influencer la manière dont vous et vos coéquipiers pensez au code.
Par exemple, prenons cette fonction :
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
🤓 Other versions and variants
def say_hi(name: str | None):
print(f"Hey {name}!")
Le paramètre name est défini comme Optional[str], mais il n'est pas optionnel, vous ne pouvez pas appeler la fonction sans le paramètre :
say_hi() # Oh, non, cela lève une erreur ! 😱
Le paramètre name est toujours requis (pas optionnel) parce qu'il n'a pas de valeur par défaut. Néanmoins, name accepte None comme valeur :
say_hi(name=None) # Cela fonctionne, None est valide 🎉
La bonne nouvelle, c'est qu'une fois sur Python 3.10, vous n'aurez plus à vous en soucier, car vous pourrez simplement utiliser | pour définir des unions de types :
def say_hi(name: str | None):
print(f"Hey {name}!")
🤓 Other versions and variants
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
Et vous n'aurez plus à vous préoccuper de noms comme «Optional» et «Union». 😎
Types génériques¶
Ces types qui prennent des paramètres de type entre crochets sont appelés types génériques ou Generics, par exemple :
Vous pouvez utiliser les mêmes types intégrés comme génériques (avec des crochets et des types à l'intérieur) :
listtuplesetdict
Et, comme avec les versions précédentes de Python, ceux du module typing :
UnionOptional- ... et d'autres.
En Python 3.10, comme alternative à l'utilisation des génériques Union et Optional, vous pouvez utiliser la barre verticale (|) pour déclarer des unions de types, c'est bien mieux et plus simple.
Vous pouvez utiliser les mêmes types intégrés comme génériques (avec des crochets et des types à l'intérieur) :
listtuplesetdict
Et les génériques du module typing :
UnionOptional- ... et d'autres.
Classes comme types¶
Vous pouvez également déclarer une classe comme type d'une variable.
Disons que vous avez une classe Person, avec un nom :
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
Vous pouvez ensuite déclarer une variable de type Person :
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
Et là encore, vous obtenez tout le support de l'éditeur :

Notez que cela signifie que «one_person est une instance de la classe Person».
Cela ne signifie pas «one_person est la classe appelée Person».
Modèles Pydantic¶
Pydantic est une bibliothèque Python pour effectuer la validation de données.
Vous déclarez la «forme» des données sous forme de classes avec des attributs.
Et chaque attribut a un type.
Ensuite, vous créez une instance de cette classe avec certaines valeurs et elle validera les valeurs, les convertira dans le type approprié (le cas échéant) et vous donnera un objet avec toutes les données.
Et vous bénéficiez de tout le support de l'éditeur avec cet objet résultant.
Un exemple tiré de la documentation officielle de Pydantic :
from datetime import datetime
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: datetime | None = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
🤓 Other versions and variants
from datetime import datetime
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
Info
Pour en savoir plus à propos de Pydantic, consultez sa documentation.
FastAPI est entièrement basé sur Pydantic.
Vous verrez beaucoup plus de tout cela en pratique dans le Tutoriel - Guide utilisateur.
Astuce
Pydantic a un comportement particulier lorsque vous utilisez Optional ou Union[Something, None] sans valeur par défaut. Vous pouvez en lire davantage dans la documentation Pydantic à propos des champs Optional requis.
Annotations de type avec métadonnées¶
Python propose aussi une fonctionnalité qui permet d'ajouter des métadonnées supplémentaires dans ces «type hints» en utilisant Annotated.
Depuis Python 3.9, Annotated fait partie de la bibliothèque standard, vous pouvez donc l'importer depuis typing.
from typing import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
Python lui-même ne fait rien avec cet Annotated. Et pour les éditeurs et autres outils, le type est toujours str.
Mais vous pouvez utiliser cet espace dans Annotated pour fournir à FastAPI des métadonnées supplémentaires sur la façon dont vous souhaitez que votre application se comporte.
L'important à retenir est que le premier paramètre de type que vous passez à Annotated est le type réel. Le reste n'est que des métadonnées pour d'autres outils.
Pour l'instant, vous avez seulement besoin de savoir que Annotated existe et que c'est du Python standard. 😎
Plus tard, vous verrez à quel point cela peut être puissant.
Astuce
Le fait que ce soit du Python standard signifie que vous aurez toujours la meilleure expérience développeur possible dans votre éditeur, avec les outils que vous utilisez pour analyser et refactoriser votre code, etc. ✨
Et aussi que votre code sera très compatible avec de nombreux autres outils et bibliothèques Python. 🚀
Les annotations de type dans FastAPI¶
FastAPI tire parti de ces «type hints» pour faire plusieurs choses.
Avec FastAPI, vous déclarez des paramètres avec des «type hints» et vous obtenez :
- du support de l'éditeur.
- des vérifications de type.
... et FastAPI utilise les mêmes déclarations pour :
- Définir les prérequis : depuis les paramètres de chemin des requêtes, les paramètres de requête, les en-têtes, les corps, les dépendances, etc.
- Convertir les données : depuis la requête vers le type requis.
- Valider les données : provenant de chaque requête :
- Générer des erreurs automatiques renvoyées au client lorsque les données sont invalides.
- Documenter l'API avec OpenAPI :
- ce qui est ensuite utilisé par les interfaces utilisateur de documentation interactive automatiques.
Tout cela peut sembler abstrait. Ne vous inquiétez pas. Vous verrez tout cela en action dans le Tutoriel - Guide utilisateur.
L'important, c'est qu'en utilisant les types standard de Python, en un seul endroit (au lieu d'ajouter davantage de classes, de décorateurs, etc.), FastAPI fera une grande partie du travail pour vous.
Info
Si vous avez déjà parcouru tout le tutoriel et êtes revenu pour en savoir plus sur les types, une bonne ressource est l'aide-mémoire de mypy.