2010-09-17 6 views
28

Possible en double:
'has_key()' or 'in'?déterminer si une clé est présente dans un dictionnaire

j'ai un comme dictionnaire Python:

mydict = {'name':'abc','city':'xyz','country','def'} 

Je veux vérifier si un La clé est dans le dictionnaire ou pas. Je suis impatient de savoir ce qui est plus préférable des deux cas suivants et pourquoi?

1> if mydict.has_key('name'): 
2> if 'name' in mydict: 
+5

BTW, 'dict 'est le nom d'un type Python intégré, il est donc préférable de ne pas l'utiliser en tant que nom de la variable dans vos scripts (bien que ce soit strictement légal, il est légal de le faire). – martineau

+4

[les docs sont assez clairs] (http://docs.python.org/library/stdtypes.html#dict.has_key), non? – SilentGhost

+0

En Python 3, les objets 'dict' n'ont plus de méthode' has_key() ', donc la portabilité de la version est meilleure. – martineau

Répondre

42
if 'name' in mydict: 

est la préférée, la version pythonique. L'utilisation de has_key() est déconseillée, et cette méthode has been removed in Python 3.

+2

De plus, '" name "dans dict' fonctionnera avec tous les dictionnaires itérables et pas seulement. –

+2

Qu'en est-il de 'dict.get (clé)'? Cela devrait également être évité? – user225312

+7

@PulpFiction: 'dict.get (key)' peut être utile quand vous (1) ne voulez pas une 'KeyError' dans le cas où' key' n'est pas dans dict (2) voulez utiliser une valeur par défaut s'il y a n'est pas 'key' (' dict.get (clé, défaut) '). Le point # 2 peut aussi être fait en utilisant 'defaultdict'. –

13

En termes de bytecode, in enregistre 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    

Mes sentiments sont que in est beaucoup plus lisible et doit être préféré dans tous les cas que je peux penser.

En termes de performance, le calendrier reflète l'opcode

$ python -mtimeit -s'd = dict((i, i) for i in range(10000))' "'foo' in d" 
10000000 loops, best of 3: 0.11 usec per loop 

$ python -mtimeit -s'd = dict((i, i) for i in range(10000))' "d.has_key('foo')" 
    1000000 loops, best of 3: 0.205 usec per loop 

in est presque deux fois plus vite.

+1

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

+2

@Mike Graham, vous avez surtout raison. Je me suis penchée sur le pire des cas, parce que, OMI, c'est là que vous voulez vraiment savoir. Aussi, je pense que votre attitude est (tout en étant absolument correcte), légèrement plus appropriée à une langue comme C où c'est rapide de toute façon à moins que vous ne fassiez vraiment quelque chose. En Python, il vaut mieux le faire correctement. En outre, les principaux développeurs ont une façon de régler "la bonne façon" de faire quelque chose, de sorte que, encore une fois, la performance est un bon indicateur de bon style dans une plus grande mesure que la normale dans un langage. – aaronasterling

7

Ma réponse est "ni un". Je crois que la façon la plus "pythonique" de faire est de NE PAS vérifier au préalable si la clé est dans un dictionnaire et d'écrire simplement du code supposant qu'elle existe et d'attraper les KeyErrors levées parce que ce n'était pas le cas.

Cela se fait habituellement avec renfermant le code dans une clause try...except et est un langage bien connu généralement exprimé en « Il est plus facile de demander pardon que la permission » ou avec l'acronyme EAFP, ce qui signifie essentiellement est Il vaut mieux essayer quelque chose et attraper les erreurs au lieu de s'assurer que tout va bien 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 avec élégance au lieu d'essayer de les éviter? Parce que c'est souvent plus lisible et que le code a tendance à être plus rapide si la probabilité est faible que la clé ne sera pas là (ou quelles que soient les conditions préalables).

Bien sûr, ce n'est pas approprié dans toutes les situations et tout le monde n'est pas d'accord avec la philosophie, vous devrez donc décider pour vous-même au cas par cas. Il n'est pas étonnant que le contraire soit appelé LBYL pour "Look Before You Leap".

Par exemple trivial considérer:

if 'name' in dct: 
    value = dct['name'] * 3 
else: 
    logerror('"%s" not found in dictionary, using default' % name) 
    value = 42 

vs

try: 
    value = dct['name'] * 3 
except KeyError: 
    logerror('"%s" not found in dictionary, using default' % name) 
    value = 42 

Bien que dans le cas où il est presque exactement la même quantité de code, le second ne passe pas le temps de vérifier d'abord et probablement légèrement plus rapide à cause de ça (essayez ... sauf que le bloc n'est pas totalement gratuit, donc ça ne fait probablement pas tellement de différence ici). D'une manière générale, les tests préalables peuvent souvent être beaucoup plus impliqués et les économies réalisées en ne le faisant pas peuvent être importantes. Cela dit, if 'name' in dict: est mieux pour les raisons indiquées dans les autres réponses.

Si vous êtes intéressé par le sujet, ce message intitulé « EAFP vs LBYL (a Re: Un peu déçu jusqu'à présent) » de l'archive de la liste de diffusion Python explique probablement la différence entre les deux approché mieux que moi avoir ici. Il ya aussi une bonne discussion sur les deux approches dans le livre Python in a Nutshell, 2e Ed par Alex Martelli dans le chapitre sur les exceptions intitulé "Error-Checking Strategies".

+1

Existe-t-il des données à l'appui de l'affirmation selon laquelle «les économies réalisées en ne le faisant pas peuvent être importantes»? En tant que développeur Java, j'ai l'habitude de penser que les exceptions sont coûteuses et devraient être pour des situations vraiment exceptionnelles. Votre recommandation ressemble à "exception goto". Pouvez-vous citer une source? – duffymo

+0

@duffymo. Non, je ne peux pas citer une source. J'ai fait cette déclaration parce qu'il y a généralement plusieurs façons par lesquelles quelque chose peut mal tourner vers un nombre relativement faible de bonnes manières. La vérification de tous les mauvais moyens peut impliquer beaucoup de code (ce qui est souvent aussi fastidieux à écrire). La gestion des exceptions peut être lente dans certaines langues et j'ai spécifiquement mentionné que cela ne serait peut-être pas une bonne solution si vous vous attendiez à ce qu'elles se produisent fréquemment. Je ne préconise pas non plus l'utilisation d'exceptions dans le cadre du contrôle normal ou régulier d'un programme - elles devraient être utilisées, comme vous le dites, dans des circonstances exceptionnelles. – martineau

+4

Les exceptions en Python sont chères. Si vous vous attendez à ce que la clé soit manquante plus de quelques pour cent du temps, le coût d'exception dominera probablement l'exécution de la fonction. –

18

Dans la même veine 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) 

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

Un autre cas commun est quelque chose comme

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

d[x].append(foo) 

qui peut être réécrite

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

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

d[x].append(foo) 
+0

Oui , quelque chose que vous pourriez appeler "défauts intelligents" (ou même "design intelligent" ;-) – martineau

+3

Naw, ce sont des méthodes et des types Python évolué au fil du temps. ';)' –

+0

OMG, vous avez raison! – martineau