Dois-je utiliser 'has_key ()' ou 'in' sur les textes Python?

912

Je me demande ce qu'il vaut mieux faire:

d = {'a': 1, 'b': 2}
'a' in d
True

ou:

d = {'a': 1, 'b': 2}
d.has_key('a')
True
igorgue
la source

Réponses:

1288

in est définitivement plus pythonique.

En fait has_key()a été supprimé dans 3.x Python .

tonfa
la source
3
En outre, en Python 3, pour vérifier l'existence de valeurs, au lieu des clés, essayez >>> 1 dans d.values ​​()
riza
217
Un semi-gotcha à éviter cependant est de vous assurer que vous faites: "key in some_dict" plutôt que "key in some_dict.keys ()". Les deux sont équivalents sémantiquement, mais en termes de performances, ce dernier est beaucoup plus lent (O (n) vs O (1)). J'ai vu des gens faire le "in dict.keys ()" pensant que c'est plus explicite et donc mieux.
Adam Parkin
2
@AdamParkin J'ai démontré votre commentaire dans ma réponse stackoverflow.com/a/41390975/117471
Bruno Bronosky
8
@AdamParkin En Python 3, keys()c'est juste une vue d'ensemble dans un dictionnaire plutôt qu'une copie, tout x in d.keys()comme O (1). Pourtant, x in dc'est plus Pythonic.
Arthur Tacca
2
@AdamParkin Intéressant, je ne l'ai pas vu. Je suppose que c'est parce que x in d.keys()doit construire et détruire un objet temporaire, avec l'allocation de mémoire qui en découle, où x in d.keys()fait juste une opération arithmétique (calcul du hachage) et fait une recherche. Notez que ce d.keys()n'est que 10 fois plus long que cela, ce qui n'est pas vraiment long. Je n'ai pas vérifié mais je suis toujours sûr que ce n'est que O (1).
Arthur Tacca
253

in gagne haut la main, non seulement en élégance (et en n'étant pas déprécié ;-) mais aussi en performance, par exemple:

$ python -mtimeit -s'd=dict.fromkeys(range(99))' '12 in d'
10000000 loops, best of 3: 0.0983 usec per loop
$ python -mtimeit -s'd=dict.fromkeys(range(99))' 'd.has_key(12)'
1000000 loops, best of 3: 0.21 usec per loop

Bien que l'observation suivante ne soit pas toujours vraie, vous remarquerez qu'en général , en Python, la solution la plus rapide est plus élégante et Pythonique; c'est pourquoi -mtimeitest si utile - il ne s'agit pas seulement d'économiser une centaine de nanosecondes ici et là! -)

Alex Martelli
la source
4
Merci pour cela, fait vérifier que "in some_dict" est en fait O (1) beaucoup plus facile (essayez d'augmenter le 99 pour dire 1999, et vous verrez que le temps d'exécution est à peu près le même).
Adam Parkin,
2
has_keysemble être O (1) aussi.
dan-gph
96

Selon les documents python :

has_key()est déconseillé en faveur de key in d.

Nadia Alramli
la source
1
has_key()est maintenant supprimé dans Python 3
Vadim Kotov
42

Utilisez dict.has_key()si (et seulement si) votre code doit être exécutable par les versions Python antérieures à 2.3 (lors de key in dictson introduction).

John Machin
la source
1
La mise à jour WebSphere de 2013 utilise Jython 2.1 comme langage de script principal. Donc, c'est malheureusement encore une chose utile à noter, cinq ans après l'avoir noté.
ArtOfWarfare
23

Il y a un exemple où intue réellement votre performance.

Si vous utilisez insur un conteneur O (1) qui implémente uniquement __getitem__et has_key()non, __contains__vous transformerez une recherche O (1) en recherche O (N) (comme cela inrevient à une recherche linéaire via __getitem__).

Le correctif est évidemment trivial:

def __contains__(self, x):
    return self.has_key(x)
schlenk
la source
6
Cette réponse était applicable lorsqu'elle a été publiée, mais 99,95% des lecteurs peuvent l'ignorer en toute sécurité. Dans la plupart des cas, si vous travaillez avec quelque chose d'aussi obscur, vous le saurez.
wizzwizz4
2
Ce n'est vraiment pas un problème. has_key()est spécifique aux dictionnaires Python 2 . in/ __contains__est la bonne API à utiliser; pour les conteneurs où une analyse complète est inévitable, il n'y a de toute façon pas de has_key()méthode , et s'il existe une approche O (1), ce sera spécifique au cas d'utilisation et donc au développeur de choisir le bon type de données pour le problème.
Martijn Pieters
15

has_keyest une méthode de dictionnaire, mais infonctionnera sur n'importe quelle collection, et même lorsqu'elle __contains__est manquante, inutilisera toute autre méthode pour itérer la collection pour le découvrir.

u0b34a0f6ae
la source
1
Et fonctionne également sur les itérateurs "x dans xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae
1
…: Cela ressemble à une très mauvaise idée: 50 opérations au lieu de 2.
Clément
1
@ Clément En Python 3, il est en fait assez efficace de faire des intests sur des rangeobjets. xrangeCependant, je ne suis pas sûr de son efficacité sur Python 2 . ;)
PM 2Ring
@ Clément pas en Python 3; __contains__peut trivialement calculer si une valeur est dans la plage ou non.
Martijn Pieters
1
@AlexandreHuat Votre timing comprend les frais généraux de création d'une nouvelle rangeinstance à chaque fois. En utilisant une seule instance préexistante , le test "nombre entier dans la plage" est environ 40% plus rapide dans mes délais.
MisterMiyagi
14

La solution à dict.has_key () est déconseillée, utilisez 'in' - sublime text editor 3

Ici, j'ai pris un exemple de dictionnaire nommé «âges» -

ages = {}

# Add a couple of names to the dictionary
ages['Sue'] = 23

ages['Peter'] = 19

ages['Andrew'] = 78

ages['Karren'] = 45

# use of 'in' in if condition instead of function_name.has_key(key-name).
if 'Sue' in ages:

    print "Sue is in the dictionary. She is", ages['Sue'], "years old"

else:

    print "Sue is not in the dictionary"
Greena modi
la source
6
Correct, mais il a déjà été répondu, bienvenue dans Stackoveflow, merci pour l'exemple, vérifiez toujours les réponses!
igorgue
@igorgue je ne suis pas sûr des downvotes pour elle. Sa réponse pourrait être similaire à celles déjà répondues, mais elle donne un exemple. N'est-ce pas assez digne d'être une réponse de SO?
Akshat Agarwal
14

Développant les tests de performance d'Alex Martelli avec les commentaires d'Adam Parkin ...

$ python3.5 -mtimeit -s'd=dict.fromkeys(range( 99))' 'd.has_key(12)'
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 301, in main
    x = t.timeit(number)
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
    d.has_key(12)
AttributeError: 'dict' object has no attribute 'has_key'

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(  99))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0872 usec per loop

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(1999))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0858 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d'
10000000 loops, best of 3: 0.031 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d'
10000000 loops, best of 3: 0.033 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d.keys()'
10000000 loops, best of 3: 0.115 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d.keys()'
10000000 loops, best of 3: 0.117 usec per loop
Bruno Bronosky
la source
De merveilleuses statistiques, parfois implicites pourraient être meilleures qu'explicites (au moins en
termes d'
Merci, @varun. J'avais oublié cette réponse. Je dois faire ce genre de test plus souvent. Je lis régulièrement de longs fils de discussion où les gens discutent de la meilleure façon de faire les choses. Mais je me souviens rarement à quel point c'était facile d'obtenir des preuves .
Bruno Bronosky
0

Si vous avez quelque chose comme ça:

t.has_key(ew)

changez-le ci-dessous pour fonctionner sur Python 3.X et supérieur:

key = ew
if key not in t
Harshita Jhavar
la source
6
Non, vous avez inversé le test. t.has_key(ew)renvoie Truesi la ewréférence des valeurs est également une clé dans le dictionnaire. key not in trenvoie Truesi la valeur n'est pas dans le dictionnaire. De plus, l' key = ewalias est très, très redondant. L'orthographe correcte est if ew in t. C'est ce que la réponse acceptée de 8 ans auparavant vous a déjà dit.
Martijn Pieters