Un dictionnaire Python se maîtrise avec quelques réflexes simples. J’évite les KeyError inutiles, je groupe mes données proprement, je fusionne sans bricoler, je passe mes paramètres plus vite et je sécurise mes structures avec TypedDict. C’est souvent là que le code devient vraiment lisible.
Comment éviter les erreurs de clé ?
J’évite les erreurs de clé avec dict.get() quand l’absence d’une clé est normale ou attendue.
Les dictionnaires Python sont partout dans le code courant. Dans une config, un JSON, un payload d’API, le résultat d’un script d’automatisation, une réponse d’outil no-code, bref, dès qu’on manipule des données un peu souples. Le piège arrive souvent au même endroit : je suppose qu’une clé existe, et Python me rappelle que non avec une KeyError.
Avec les crochets, je dis à Python : “Cette clé doit être là”. Si elle n’existe pas, le programme échoue. Et parfois, c’est exactement ce que je veux. Parce qu’une clé manquante peut révéler une vraie anomalie métier ou technique.
Avec .get(), je dis plutôt : “Cette clé peut manquer, et dans ce cas je veux une valeur de secours”. C’est très pratique pour les champs optionnels.
user = {
"name": "Sarah",
"email": "sarah@example.com"
}
role = user.get("role", "viewer")
print(role) # viewer
Ici, role est optionnel. Si l’API ne me l’envoie pas, je continue proprement avec viewer. C’est clair, lisible, et ça évite de mettre un try except juste pour gérer un cas normal.
Mais je n’utilise pas .get() partout. C’est une mauvaise habitude que je vois souvent chez des équipes qui veulent “éviter les erreurs”. En vrai, elles cachent parfois des bugs. Si une clé est obligatoire, je préfère voir l’erreur tout de suite.
user = {
"user_id": 42,
"name": "Sarah"
}
user_id = user["user_id"]
print(user_id)
Dans cet exemple, user_id doit absolument exister. Si cette clé manque, je veux une KeyError. Ça veut dire que ma donnée est invalide, ou que quelque chose a changé dans la source. J’ai déjà vu ça sur un flux API client : un champ renommé côté fournisseur, et les .get() partout avaient masqué le problème pendant plusieurs jours.
Le vrai sujet, ce n’est pas “crochets ou .get()”. C’est l’intention. Si la clé est obligatoire, je veux voir l’erreur. Si elle est optionnelle, je veux une valeur claire.
| Situation | Méthode à utiliser |
| La clé est obligatoire et son absence est une anomalie | mon_dict[clé] |
| La clé est optionnelle et une valeur de secours est prévue | mon_dict.get(clé, valeur_par_defaut) |
Comment compter et grouper sans bricoler ?
J’utilise collections.defaultdict quand je dois compter ou grouper des éléments sans initialiser chaque clé à la main.
Après avoir sécurisé les accès aux clés, on tombe vite sur un autre cas très classique avec les dictionnaires Python : construire des données progressivement. Un compteur qui augmente, une liste qui se remplit, des lignes qu’on classe par type… Et là, avec un dictionnaire classique, on commence souvent à bricoler.
Le problème est simple. Quand une clé n’existe pas encore, il faut vérifier sa présence avant d’ajouter quelque chose. Ça marche, mais ça alourdit vite le code.
mots = ["python", "data", "python", "ia", "data", "python"]
compteur = {}
for mot in mots:
if mot not in compteur:
compteur[mot] = 0
compteur[mot] += 1
print(compteur)
# {'python': 3, 'data': 2, 'ia': 1}
Avec defaultdict(int), je dis juste à Python : si la clé n’existe pas, crée une valeur par défaut avec int. Et int(), ça donne 0.
from collections import defaultdict
mots = ["python", "data", "python", "ia", "data", "python"]
compteur = defaultdict(int)
for mot in mots:
compteur[mot] += 1
print(dict(compteur))
# {'python': 3, 'data': 2, 'ia': 1}
C’est plus court, plus lisible, et surtout ça évite le bruit autour de la vraie logique.
Le même principe marche très bien pour grouper des éléments. Avec defaultdict(list), Python crée une liste vide quand une catégorie apparaît pour la première fois.
from collections import defaultdict
produits = [
{"nom": "Clavier", "categorie": "Informatique"},
{"nom": "Souris", "categorie": "Informatique"},
{"nom": "Café", "categorie": "Épicerie"},
{"nom": "Thé", "categorie": "Épicerie"},
]
par_categorie = defaultdict(list)
for produit in produits:
par_categorie[produit["categorie"]].append(produit["nom"])
print(dict(par_categorie))
# {'Informatique': ['Clavier', 'Souris'], 'Épicerie': ['Café', 'Thé']}
Dans des scripts data ou automatisation, j’ai souvent vu des boucles trois fois trop longues juste parce que personne n’utilisait defaultdict. Ce n’est pas magique. C’est juste le bon outil au bon endroit.
Pour du comptage pur, collections.Counter peut aussi être très adapté. Il est fait pour compter. Mais defaultdict reste plus souple dès qu’on veut construire autre chose qu’un simple compteur.
- Compter des mots, des statuts, des erreurs, des événements.
- Grouper des lignes par catégorie, client, date ou type.
- Accumuler des listes, des totaux ou des objets au fil d’une boucle.
- Réduire le code répétitif quand les clés arrivent progressivement.
Comment fusionner des dictionnaires proprement ?
Je fusionne proprement deux dictionnaires avec l’opérateur | en Python moderne, et avec |= quand je veux modifier le dictionnaire existant.
Après avoir lu et construit des dictionnaires, on finit vite par devoir les combiner. Ça arrive tout le temps avec des configs, des options utilisateur, des paramètres d’appel API, ou des données qu’on enrichit étape par étape. C’est un cas banal, mais si c’est mal écrit, ça devient vite flou.
Depuis Python 3.9, l’opérateur | fait ça très bien. Il crée un nouveau dictionnaire à partir de deux dictionnaires. Si une clé existe des deux côtés, la valeur du dictionnaire de droite gagne. C’est simple à lire : “je prends celui de gauche, puis je le complète ou je l’écrase avec celui de droite”.
default_config = {
"timeout": 30,
"language": "fr",
"retries": 3
}
user_config = {
"timeout": 10,
"language": "en"
}
config = default_config | user_config
print(config)
# Résultat attendu :
# {"timeout": 10, "language": "en", "retries": 3}
Ici, user_config remplace timeout et language. Le reste vient de default_config. J’aime bien cette écriture parce qu’elle dit exactement ce qu’elle fait, sans bruit autour.
Quand je veux modifier le dictionnaire existant directement, j’utilise |=. C’est une fusion “in-place”, c’est-à-dire que Python ne crée pas une nouvelle variable finale, il met à jour celle qui existe déjà.
config = {
"timeout": 30,
"language": "fr",
"debug": False
}
env_config = {
"debug": True,
"timeout": 5
}
config |= env_config
print(config)
# Résultat attendu :
# {"timeout": 5, "language": "fr", "debug": True}
Avant Python 3.9, on voyait souvent update() ou le déballage avec {**a, **b}. Ça marche encore. Mais je trouve que | rend l’intention plus lisible, surtout dans du code métier où on veut comprendre vite.
a = {"x": 1}
b = {"y": 2}
merged = {**a, **b}
# Ancienne approche, résultat attendu : {"x": 1, "y": 2}
a.update(b)
# Modifie a directement, résultat attendu : {"x": 1, "y": 2}
Attention quand même, ce n’est pas une fusion profonde. Si une valeur est elle-même un dictionnaire imbriqué, le dictionnaire de droite remplace toute la valeur pour cette clé. Python ne va pas fusionner automatiquement les sous-clés.
a = {"api": {"timeout": 30, "retries": 3}}
b = {"api": {"timeout": 10}}
result = a | b
print(result)
# Résultat attendu :
# {"api": {"timeout": 10}}
| Syntaxe | Usage principal |
| a | b | Créer un nouveau dictionnaire fusionné, avec les clés de droite prioritaires. |
| a |= b | Modifier directement le dictionnaire existant. |
| a.update(b) | Ancienne approche pour modifier un dictionnaire en place. |
Comment passer un dict à une fonction ?
Je passe un dictionnaire à une fonction avec ** quand ses clés correspondent aux noms des paramètres attendus par la fonction.
C’est très pratique quand vous avez déjà préparé vos données dans un dict, par exemple après une fusion, un nettoyage, ou une réponse d’API. Au lieu d’écrire chaque argument à la main, Python peut “déballer” le dictionnaire pour vous.
def connect(host, port):
return f"Connexion à {host}:{port}"
config = {
"host": "localhost",
"port": 5432
}
connect(**config)
# Équivaut à connect(host="localhost", port=5432)
Le **kwargs, dans l’idée, veut dire “prends les paires clé-valeur du dictionnaire et transforme-les en arguments nommés”. Une clé devient le nom du paramètre, sa valeur devient la valeur passée à ce paramètre.
def create_user(name, email, role="viewer"):
return {
"name": name,
"email": email,
"role": role
}
data = {
"name": "Alice",
"email": "alice@example.com"
}
user = create_user(**data)
Ici, name et email sont passés automatiquement. Comme la clé role n’est pas présente dans le dictionnaire, Python garde la valeur par défaut « viewer ». C’est exactement ce qu’on veut dans pas mal de cas métier.
Attention quand même, si le dictionnaire contient une clé que la fonction n’attend pas, Python lève une erreur.
data = {
"name": "Alice",
"email": "alice@example.com",
"age": 32
}
create_user(**data)
# TypeError: create_user() got an unexpected keyword argument 'age'
Et franchement, cette erreur est utile. Elle force à garder un contrat propre entre les données et le code. J’ai vu ça souvent chez des clients avec des payloads d’API qui grossissent avec le temps. Une clé ajoutée côté API, et tout d’un coup une fonction reçoit un champ qu’elle ne sait pas gérer. Mieux vaut le voir vite.
La différence avec * est simple. * déballe une séquence, comme une liste ou un tuple, en arguments positionnels. ** déballe un dictionnaire en arguments nommés. Pour des payloads d’API, des paramètres de configuration, de la création d’objets ou des automatisations, ** évite beaucoup de code répétitif.
- Vérifiez que les clés du dictionnaire ont exactement les mêmes noms que les paramètres.
- Filtrez les clés en trop avant l’appel si les données viennent d’une API ou d’un formulaire.
- Gardez des valeurs par défaut dans la fonction quand certains champs sont optionnels.
- Évitez de passer un dict énorme si la fonction n’a besoin que de trois champs.
Comment rendre un dictionnaire plus lisible et typé ?
Je rends un dictionnaire plus lisible avec l’opérateur walrus := quand il évite une recherche répétée, et plus robuste avec TypedDict quand la structure des données doit être claire.
Après les accès, le groupement, la fusion et le passage en arguments, il reste deux sujets qui font vraiment passer un cap. Éviter les répétitions inutiles. Et documenter la forme attendue des dictionnaires. C’est souvent là que les petits scripts deviennent du code maintenable.
L’opérateur walrus, introduit en Python 3.8, permet d’affecter une valeur directement dans une expression. Dit simplement, je peux récupérer une valeur dans un dictionnaire pendant un if, sans refaire deux fois le même get.
profile = {
'name': 'Alice',
'email': 'alice@example.com'
}
if email := profile.get('email'):
print(f"Email trouvé : {email}")
Sans ça, on voit souvent ce genre de code avec profile.get(’email’) appelé dans la condition puis rappelé juste après. Ce n’est pas dramatique, mais ça ajoute du bruit. Sur une structure imbriquée, le gain devient plus visible.
response = {
'user': {
'id': 42,
'plan': {
'name': 'pro',
'active': True
}
}
}
if user := response.get('user'):
if plan := user.get('plan'):
if plan.get('active'):
print(f"Plan actif : {plan.get('name')}")
Je garde quand même une limite simple. Le walrus est utile si le code devient plus clair. S’il rend la condition difficile à lire, je préfère deux lignes explicites. J’ai déjà vu des conditions “intelligentes” devenir impossibles à relire trois semaines plus tard, et franchement, personne ne gagne.
TypedDict, lui, vient du module typing. Ça ne transforme pas un dictionnaire en classe. Ça permet de déclarer les clés attendues et les types associés pour les outils de typage, comme les analyseurs statiques et les IDE. En clair, votre éditeur peut vous prévenir si vous écrivez une mauvaise clé ou si vous passez une mauvaise valeur.
from typing import TypedDict
class UserData(TypedDict):
id: int
name: str
email: str
def send_welcome_email(user: UserData) -> None:
print(f"Bonjour {user['name']}, email envoyé à {user['email']}")
user = {
'id': 1,
'name': 'Alice',
'email': 'alice@example.com'
}
send_welcome_email(user)
Dans les projets data, API ou automatisation, ça évite pas mal d’erreurs bêtes. Une clé manquante. Une faute de frappe. Un id reçu en texte alors que le reste du code attend un entier. C’est discret, mais ça change la qualité du code.
| Problème | Outil | Bénéfice |
| Recherche répétée dans un dictionnaire | Walrus := | Code plus court et plus lisible quand l’affectation reste simple |
| Structure de dictionnaire floue | TypedDict | Clés et types documentés, avec aide de l’IDE et du typage statique |
Alors, votre dictionnaire Python dit quoi de votre code ?
Un dictionnaire Python, ça peut rester un simple conteneur clé valeur, ou devenir un vrai point de solidité dans votre code. J’utilise .get() quand l’absence d’une clé est normale, defaultdict pour compter et grouper sans bruit, | pour fusionner clairement, ** pour passer des paramètres proprement, le walrus quand il simplifie vraiment, et TypedDict quand la structure compte. Rien de spectaculaire, mais c’est exactement ce qui rend un script plus fiable. Le bénéfice pour vous est simple, moins d’erreurs cachées, moins de code inutile, et un Python plus facile à maintenir.
FAQ
- Quand utiliser .get() plutôt que les crochets avec un dictionnaire Python ?
J’utilise .get() quand une clé peut manquer sans que ce soit une erreur grave. Ça me permet de définir une valeur par défaut et d’éviter une KeyError. J’utilise les crochets quand la clé est obligatoire et que je veux que le code échoue si elle manque. - À quoi sert defaultdict en Python ?
defaultdict sert à créer automatiquement une valeur par défaut pour une clé absente. C’est très pratique pour compter des éléments avec defaultdict(int) ou grouper des données avec defaultdict(list), sans écrire des tests partout dans la boucle. - Comment fusionner deux dictionnaires en Python moderne ?
Avec Python 3.9 et plus, j’utilise l’opérateur | pour créer un nouveau dictionnaire fusionné. Si une même clé existe dans les deux dictionnaires, la valeur du dictionnaire de droite remplace celle de gauche. Pour modifier le dictionnaire existant, j’utilise |=. - Comment passer un dictionnaire en arguments de fonction ?
J’utilise ** devant le dictionnaire, à condition que ses clés correspondent aux paramètres de la fonction. C’est utile pour passer des configs ou des données préparées. Si une clé manque, la fonction peut utiliser sa valeur par défaut. Si une clé en trop est présente, Python peut lever une erreur. - TypedDict rend-il un dictionnaire Python plus sûr ?
TypedDict aide surtout les outils de typage et les IDE à vérifier la structure attendue d’un dictionnaire. Je peux déclarer les clés et les types attendus, ce qui réduit les fautes de frappe, les clés manquantes et les incohérences dans les projets data, API ou automatisation.
A propos de l’auteur
Je suis Franck Scandolera, expert et formateur en Tracking avancé server-side, Analytics Engineering, automatisation No/Low Code avec n8n, intégration de l’IA en entreprise et SEO/GEO. J’accompagne des équipes qui manipulent beaucoup de données, d’API, de scripts Python et de pipelines automatisés. J’ai travaillé avec des clients comme Logis Hôtel, Yelloh Village, BazarChic, la Fédération Française de Football ou Texdecor. Je dirige l’agence webAnalyste et l’organisme Formations Analytics. Si vous voulez fiabiliser vos données, vos automatisations ou vos usages IA, contactez-moi, je peux vous aider.
⭐ Data Analyst, Analytics Engineer et expert dans l’automatisation IA ⭐
Ref clients : Logis Hôtel, Yelloh Village, BazarChic, Fédération Football Français, Texdecor…
Mon terrain de jeu :
Data Analyst & Analytics engineering : tracking propre RGPD, entrepôt de données (GTM server, BigQuery…), modèles (dbt/Dataform), dashboards décisionnels (Looker, SQL, Python).
Automatisation IA des taches Data, Marketing, RH, compta etc : conception de workflows intelligents robustes (n8n, Make, App Script, scraping) connectés aux API de vos outils et LLM (OpenAI, Mistral, Claude…).
Engineering IA pour créer des applications et agent IA sur mesure : intégration de LLM (OpenAI, Mistral…), RAG, assistants métier, génération de documents complexes, APIs, backends Node.js/Python.





