KeyError en Python : d'où vient l'erreur et comment la corriger

KeyError signale une clé absente d'un dictionnaire. D'où elle vient, comment la lire, et quand utiliser get plutôt que les crochets.
5 min de lecture
Believemy logo

Définition

Un dictionnaire ne connaît que les clés qu'on lui a données. Tant qu'on reste dans cet ensemble, la clé pointe vers sa valeur. Le problème survient quand le code interroge une clé jamais enregistrée, par erreur de frappe, par oubli, ou parce que la donnée attendue n'est jamais arrivée. Que doit alors renvoyer Python ?

Il aurait pu renvoyer None sans un mot, et laisser le programme continuer avec une valeur creuse. Il fait l'inverse : il lève KeyError et arrête tout, à l'endroit exact de la lecture fautive. Une clé absente signale presque toujours une donnée manquante, et laisser passer un None à sa place déplacerait le problème plus loin, là où il serait bien plus dur à relier à sa cause.

Voici à quoi ça ressemble sur un dictionnaire de deux entrées, quand on lit une troisième qui n'existe pas :

PYTHON
# Le dictionnaire ne connaît que deux clés
client = {"nom": "Dupont", "ville": "Lyon"}
client["email"]

# KeyError: 'email'

Le message tient en un mot : la clé demandée, entre guillemets simples. C'est l'un des rares messages Python à afficher directement la valeur fautive, ce qui rend le diagnostic presque immédiat. Son équivalent pour les listes ou les chaînes de caractères est l'IndexError : même refus, appliqué à une position plutôt qu'à un nom.


D'où vient une clé manquante

Une clé manquante n'apparaît presque jamais par hasard. Elle vient presque toujours de l'une de ces trois situations, et savoir laquelle guide la correction.

La première est l'écart d'écriture. Une clé est comparée caractère par caractère : "Email", "email" et "email " sont trois clés distinctes, et l'espace de fin reste invisible à la relecture. Le type compte tout autant : 1 et "1" ne désignent jamais la même entrée.

La deuxième vient de la donnée elle-même. Un champ facultatif d'une réponse json peut exister sur les neuf cents premiers enregistrements et disparaître sur le neuf cent unième, parce qu'un utilisateur ne l'a jamais rempli. Le code n'a pas changé, les données si, d'où cette erreur si fréquente en production alors que les tests passaient.

La troisième vient d'une clé construite plutôt que tapée en dur, par concaténation ou dans une boucle. Dès qu'une clé se calcule, elle peut se calculer faux, sans qu'aucune erreur ne se déclare à ce moment-là : le problème ne remonte qu'à la lecture, parfois dans une autre fonction.

Attention

Entourer la lecture d'un try muet, qui attrape KeyError sans rien faire d'autre que continuer, fait disparaître le message avec l'erreur. Le programme tourne avec une valeur manquante quelque part, et le bug ressurgit plus tard, sous une forme qui ne rappelle plus un dictionnaire.


Les écritures qui évitent l'erreur

Une fois la cause identifiée, reste à choisir comment réagir face à une clé qui peut manquer. Cinq écritures existent, et elles ne répondent pas à la même situation.

ÉcritureCe qu'elle apporte
dico.get("cle")Renvoie None au lieu de lever
dico.get("cle", 0)Renvoie la valeur de repli de votre choix
"cle" in dicoVérifie la présence avant de lire
try et except KeyErrorRattrape le cas et décide de la suite
dico.setdefault("cle", [])Crée l'entrée au premier accès

get convient à une clé réellement facultative, dont l'absence a un sens métier : un profil sans photo, une commande sans code promo. try et except conviennent plutôt quand l'absence est une anomalie à enregistrer avant de décider la suite. Reste une question avant de choisir : cette absence a-t-elle un sens, ou cache-t-elle un problème ailleurs ?

Bon à savoir

Quand la valeur de repli est toujours la même liste ou le même dictionnaire vide, collections.defaultdict évite de répéter setdefault à chaque écriture : le dictionnaire crée lui-même l'entrée manquante dès qu'on la lit.


Faire taire l'erreur n'est pas la corriger

Remplacer partout les crochets par get est tentant, et souvent regrettable. Une clé obligatoire absente est le symptôme d'un problème en amont : un import incomplet, un champ renommé, un filtre trop large. En la remplaçant par None, on ne corrige rien, on déplace le défaut de quelques lignes, et il ressurgira sous une forme plus dure à relier à sa cause.

La bonne question est donc celle-ci : cette clé peut-elle légitimement manquer ? Si oui, une valeur de repli explicite est la bonne réponse. Si non, laissez l'exception remonter telle quelle : le traceback nomme le fichier, la ligne et la clé fautive, ce qui suffit presque toujours à identifier ce qui a produit la donnée incomplète.


Questions fréquentes

Question

Quelle est la différence avec IndexError ?

Les deux signalent un accès à quelque chose d'absent, mais pas sur la même structure. KeyError concerne les dictionnaires et les ensembles, qui retrouvent leurs éléments par une clé nommée. IndexError concerne les listes, les tuples et les chaînes de caractères, qui font de même par une position. Une seule question tranche : noms ou rangs ?

Question

Pourquoi une clé bien visible à l'écran provoque-t-elle l'erreur ?

Parce que l'affichage ne montre ni les espaces de fin, ni la différence entre le nombre 1 et la chaîne "1", deux détails pourtant décisifs pour Python. Comparer la clé demandée avec le contenu réel de dico.keys(), plutôt qu'avec l'apparence du dictionnaire, lève le doute en une ligne.

Question

Comment savoir quelles clés le dictionnaire contient vraiment ?

Un print de dico.keys() juste avant la ligne fautive répond en une seconde. Sur une structure imbriquée, mieux vaut afficher le niveau parent que la racine entière : l'erreur porte toujours sur le dernier crochet évalué, jamais sur le premier.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.