KeyError Python : clé absente, get() ou in ?

Corrige une KeyError Python en vérifiant la clé cherchée. Distingue clé obligatoire, valeur par défaut et compteur, sans confondre absence, zéro et None.

PythonPar
Publié le

Ton programme s'arrête sur KeyError: 'coffee'. Le message te donne la clé recherchée, mais remplacer tous les crochets par get() peut cacher le problème au lieu de le corriger.

En bref

Une KeyError lors d'un accès à un dictionnaire Python ordinaire signifie que la clé demandée est absente. Vérifie la valeur exacte de cette clé et son type, puis choisis selon le contrat : d[key] si elle doit exister, d.get(key, default) si son absence est prévue, ou key in d pour tester sa présence. Une valeur 0 ou None peut appartenir à une clé présente ; elle ne prouve pas une absence.

Que signifie la clé affichée dans KeyError ?

Dans stock["coffee"], Python cherche la clé "coffee", pas la première valeur disponible. Ici, seule "tea" existe.

missing.py
stock = {"tea": 0}print(stock["coffee"])
Terminal
$ python3 missing.pyTraceback (most recent call last):  File "/home/student/lab/missing.py", line 2, in <module>    print(stock["coffee"])          ~~~~~^^^^^^^^^^KeyError: 'coffee'

Lis la dernière ligne du traceback pour connaître la clé, puis la ligne de ton fichier pour trouver l'accès qui échoue. Avec plusieurs accès sur une ligne, sépare-les temporairement : tu dois identifier quel dictionnaire est consulté à ce moment-là.

Le diagnostic concerne ici les dictionnaires ordinaires. D'autres objets peuvent aussi lever une KeyError, et une sous-classe de dictionnaire peut définir un comportement particulier pour les clés absentes.

Pourquoi la clé semble-t-elle pourtant présente ?

La chaîne "tea " contient un espace final. Ce n'est pas la clé "tea". Affiche sa représentation avec repr() pour voir les délimiteurs, puis compare les types et les clés du dictionnaire concerné.

inspect.py
stock = {"tea": 0, "1": 8}key = "tea "print(repr(key), type(key).__name__)print(list(stock))print(key in stock)print("tea" in stock)print(1 in stock, "1" in stock)
Terminal
$ python3 inspect.py'tea ' str['tea', '1']FalseTrueFalse True

Vérifie aussi la casse et l'objet consulté : deux dictionnaires peuvent avoir des contenus différents. Une chaîne "1" et l'entier 1 sont des clés différentes. Pour une liste, l'indice absent provoque plutôt une IndexError ; un dictionnaire peut tout à fait avoir une clé entière.

Ne supprime pas systématiquement les espaces et ne convertis pas toutes les clés en minuscules. Ces transformations ont un sens seulement si ton format d'entrée les autorise. Dans un comptage de caractères sensible à la casse, elles changeraient les données.

Faut-il utiliser get(), in ou les crochets ?

Choisis ce que signifie une clé absente dans ton programme. Si un champ obligatoire manque, laisser remonter l'erreur permet de détecter une entrée invalide. Si tu veux l'expliquer à l'appelant, intercepte précisément KeyError autour de l'accès concerné. Un except: englobant toute la fonction cacherait aussi d'autres erreurs.

Que signifie une clé absente ?

  • Champ obligatoire

    d[key]

    Une absence doit être détectée et corrigée dans les données ou chez l'appelant.

  • Valeur facultative

    d.get(key, default)

    Le programme a une valeur de repli définie pour ce cas.

  • Présence à tester

    key in d

    Le test porte sur la clé, même si sa valeur vaut zéro ou None.

Choisir le contrat d'accès

get() lit sans ajouter de clé. La valeur de repli est utilisée seulement si la clé manque.

lookup.py
counts = {"a": 2, "b": 1}print(counts.get("c", 0))print("c" in counts)print(counts)print(counts.get("a", 0))
Terminal
$ python3 lookup.py0False{'a': 2, 'b': 1}2

Pour enregistrer une valeur, il faut une affectation comme celle du compteur plus bas.

Pourquoi if not d.get(key) peut-il se tromper ?

Ce test porte sur la valeur renvoyée. Il est vrai pour 0, None, False ou une chaîne vide, même lorsque la clé existe.

presence.py
stock = {"tea": 0, "coffee": None}for key in ("tea", "coffee", "milk"):    print(key, key in stock, stock.get(key), not stock.get(key))
Terminal
$ python3 presence.pytea True 0 Truecoffee True None Truemilk False None True

"tea" existe et vaut zéro. "coffee" existe avec une valeur inconnue représentée par None. "milk" manque. Si ton besoin est de distinguer ces situations, teste l'appartenance.

Pour récupérer une valeur en une seule lecture et distinguer une absence d'un None stocké, une sentinelle privée convient aussi :

sentinel.py
missing = object()stock = {"coffee": None}for key in ("coffee", "milk"):    value = stock.get(key, missing)    print(key, value is missing, value is None)
Terminal
$ python3 sentinel.pycoffee False Truemilk True False

Compare la sentinelle avec is. L'objet créé pour ce test n'est aucune des valeurs déjà placées dans ce dictionnaire.

Comment initialiser un compteur sans KeyError ?

Dans un dictionnaire vide, counts[char] += 1 doit d'abord lire une valeur qui n'existe pas encore. Pour un compteur, le contrat naturel est un nombre initial de zéro, suivi d'une affectation.

counter.py
def frequencies(text):    counts = {}    for char in text:        counts[char] = counts.get(char, 0) + 1    return counts
for prefix in ("", "a", "ab", "aba"):    print(repr(prefix), frequencies(prefix))
Terminal
$ python3 counter.py'' {}'a' {'a': 1}'ab' {'a': 1, 'b': 1}'aba' {'a': 2, 'b': 1}

Sur "aba", la lecture de "a" donne zéro au premier passage, puis un au dernier. L'affectation crée la clé la première fois et remplace sa valeur la suivante.

Compter les caractères de aba

Étape 1 sur 3

Après le premier a

char
a
  1. a1créée
  2. babsente

get renvoie zéro, puis l'affectation enregistre un.

Lire toutes les étapes
  1. 1. Après le premier a

    char
    a
    1. a1créée
    2. babsente

    get renvoie zéro, puis l'affectation enregistre un.

  2. 2. Après b

    char
    b
    1. a1
    2. b1créée

    Une deuxième clé est ajoutée ; le compte de a ne change pas.

  3. 3. Après le dernier a

    char
    a
    1. a2incrémentée
    2. b1

    get lit un ; l'affectation enregistre deux sous la même clé.

Le dictionnaire vide est créé à chaque appel de frequencies. Une chaîne vide donne un dictionnaire vide. Chaque caractère est conservé tel quel : les espaces et la casse comptent.

Python

same_character_bag

32-bit

Compare les fréquences de deux chaînes. L'exercice demande les mêmes caractères avec les mêmes nombres d'occurrences ; les espaces et la casse comptent, et deux chaînes vides correspondent. Un ensemble de caractères seul perdrait les fréquences.

Ouvrir l'exercice

Pour ordonner ensuite des résultats, consulte le tri Python sur plusieurs critères. Si une modification apparaît dans plusieurs structures, le guide des copies de listes imbriquées traite cet autre mécanisme.

Questions fréquentes

get() ajoute-t-il la clé absente ?

Non. Il renvoie le repli et laisse le dictionnaire inchangé. Le compteur ajoute ou met à jour la clé avec son affectation.

Pourquoi get(key, 0) renvoie-t-il None ?

Si la clé existe avec la valeur None, Python renvoie cette valeur. Le repli 0 s'applique seulement à une clé absente.

Faut-il intercepter toutes les KeyError ?

Intercepte une absence seulement quand ton programme sait la traiter. Pour une clé obligatoire, corrige l'entrée ou signale le champ manquant ; un repli silencieux peut produire un résultat faux.

in cherche-t-il parmi les valeurs ?

Sur un dictionnaire, key in d teste les clés. Pour rechercher une valeur, il faut consulter ses valeurs, ce qui répond à une autre question.

Sources

À toi d'écrire le code.