55 votes

Déterminer si une clé est présente dans un dictionnaire

J'ai un dictionnaire Python comme mydict = {'nom':'abc','ville':'xyz','pays','def'}.

Comment puis-je vérifier si une clé se trouve dans le dictionnaire ou pas? Je connais déjà ces méthodes :

if mydict.has_key('nom'):

if 'nom' in mydict:

5 votes

Soit dit en passant, dict est le nom d'un type intégré de Python, il est donc préférable d'éviter de l'utiliser comme nom de variable dans vos scripts (bien que strictement parlant, c'est légal de le faire).

4 votes

1 votes

En Python 3, les objets dict n'ont plus de méthode has_key(), donc en termes de portabilité de version, l'opérateur in est meilleur.

83voto

Tim Pietzcker Points 146308
if 'name' in mydict:

est la version préférée et pythonique. L'utilisation de has_key() est découragée, et cette méthode a été supprimée en Python 3.

2 votes

Aussi, "nom" dans dict fonctionnera avec n'importe quel itérable et pas seulement avec des dictionnaires.

2 votes

Que diriez-vous de dict.get(key)? Cela devrait également être évité?

8 votes

@PulpFiction: La méthode dict.get(key) peut être utile lorsque vous (1) ne voulez pas obtenir une erreur de type KeyError si key n'est pas dans le dictionnaire (2) voulez utiliser une valeur par défaut s'il n'y a pas de key (dict.get(key, default)). Le point #2 peut également être réalisé en utilisant defaultdict.

40voto

Mike Graham Points 22480

Sur le même thème que la réponse de martineau, la meilleure solution est souvent de ne pas vérifier. Par exemple, le code

if x in d:
    foo = d[x]
else:
    foo = bar

est normalement écrit

foo = d.get(x, bar)

ce qui est plus court et exprime plus directement ce que vous voulez dire.

Un autre cas courant est quelque chose comme

if x not in d:
    d[x] = []

d[x].append(foo)

qui peut être réécrit

d.setdefault(x, []).append(foo)

ou réécrit encore mieux en utilisant un collections.defaultdict(list) pour d et en écrivant

d[x].append(foo)

14voto

aaronasterling Points 25749

En termes de bytecode, in sauve un LOAD_ATTR et remplace un CALL_FUNCTION par un COMPARE_OP.

>>> dis.dis(indict)
  2           0 LOAD_GLOBAL              0 (name)
              3 LOAD_GLOBAL              1 (d)
              6 COMPARE_OP               6 (in)
              9 POP_TOP             

>>> dis.dis(haskey)
  2           0 LOAD_GLOBAL              0 (d)
              3 LOAD_ATTR                1 (haskey)
              6 LOAD_GLOBAL              2 (name)
              9 CALL_FUNCTION            1
             12 POP_TOP             

Je pense que in est beaucoup plus lisible et est à privilégier dans tous les cas que je peux penser.

En termes de performances, le chronométrage reflète le bytecode

$ python -mtimeit -s'd = dict((i, i) for i in range(10000))' "'foo' in d"
 10000000 boucles, meilleure de 3: 0.11 usec par boucle

$ python -mtimeit -s'd = dict((i, i) for i in range(10000))' "d.has_key('foo')"
  1000000 boucles, meilleure de 3: 0.205 usec par boucle

in est presque deux fois plus rapide.

1 votes

Toutes les mesures de vitesse sont bien sûr spécifiques au problème, généralement sans importance, dépendantes de l'implémentation, potentiellement dépendantes de la version, et moins importantes que les problèmes de désuétude et de style.

2 votes

@Mike Graham, tu as en grande partie raison. J'ai inclus le pire des cas là-dedans parce que, à mon avis, c'est là que tu veux vraiment savoir. De plus, je pense que ton attitude est (bien que tout à fait correcte), légèrement plus appropriée pour un langage comme le C où c'est rapide de toute façon, sauf si tu fais vraiment une erreur. En Python, il vaut la peine de le faire correctement dans une plus grande mesure. De plus, les développeurs principaux ont un moyen d'accorder "la seule bonne façon" de faire quelque chose pour que, encore une fois, les performances soient un bon indicateur d'un bon style dans une plus grande mesure que d'habitude dans un langage.

10voto

martineau Points 21665

Ma réponse est "aucun des deux".

Je crois que la manière la plus "pythonique" de faire les choses est de NE PAS vérifier au préalable si la clé est dans un dictionnaire et plutôt écrire du code qui suppose qu'elle est là et attraper les KeyErrors qui sont levées parce que ce n'était pas le cas.

Cela se fait généralement en encapsulant le code dans une clause try...except et est un idiome bien connu habituellement exprimé comme "Il vaut mieux demander le pardon que la permission" ou avec l'acronyme EAFP, ce qui signifie essentiellement qu'il vaut mieux essayer quelque chose et attraper les erreurs plutôt que de vérifier que tout est OK avant de faire quoi que ce soit. Pourquoi valider ce qui n'a pas besoin d'être validé lorsque vous pouvez gérer les exceptions de manière gracieuse au lieu d'essayer de les éviter ? Parce que c'est souvent plus lisible et que le code tend à être plus rapide si la probabilité que la clé ne soit pas là (ou toute autre condition préalable) est faible.

Bien sûr, cela n'est pas approprié dans toutes les situations et tout le monde n'est pas d'accord avec cette philosophie, donc vous devrez décider par vous-même au cas par cas. Sans surprise, l'opposé de ceci est appelé LBYL pour "Look Before You Leap".

Par exemple, considérez :

if 'name' in dct:
    value = dct['name'] * 3
else:
    logerror('"%s" non trouvé dans le dictionnaire, en utilisant par défaut' % name)
    value = 42

vs

try:
    value = dct['name'] * 3
except KeyError:
    logerror('"%s" non trouvé dans le dictionnaire, en utilisant par défaut' % name)
    value = 42

Quoique dans ce cas, ce soit presque exactement la même quantité de code, le second ne perd pas de temps à vérifier d'abord et est probablement légèrement plus rapide à cause de cela (bloc try...except n'est pas totalement gratuit cependant, donc cela ne fait probablement pas une grande différence ici).

En général, tester à l'avance peut souvent être beaucoup plus impliqué et les gains réalisés en ne le faisant pas peuvent être significatifs. Cela dit, if 'name' in dict: est meilleur pour les raisons énoncées dans les autres réponses.

Si le sujet vous intéresse, ce message intitulé "EAFP vs LBYL (was Re: A little disappointed so far)" des archives de la liste de diffusion Python explique probablement mieux la différence entre les deux approches que je ne l'ai fait ici. Il y a également une bonne discussion sur les deux approches dans le livre Python in a Nutshell, 2ème édition par Alex Martelli dans le chapitre 6 sur les Exceptions intitulé Stratégies de vérification des erreurs. (Je vois qu'il y a maintenant une nouvelle 3ème édition, publiée en 2017, qui couvre à la fois Python 2.7 et 3.x).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X