¿Debo usar 'has_key ()' o 'in' en Python dicts?


912

Me pregunto qué es mejor hacer:

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

o:

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

Respuestas:


1288

in Definitivamente es más pitónico.

De hecho has_key()fue eliminado en Python 3.x .


3
Como adición, en Python 3, para verificar la existencia de valores, en lugar de las claves, intente >>> 1 en d.values ​​()
riza el

217
Sin embargo, una semi-gotcha que debe evitarse es asegurarse de hacerlo: "key in some_dict" en lugar de "key in some_dict.keys ()". Ambos son equivalentes semánticamente, pero en cuanto al rendimiento, el último es mucho más lento (O (n) frente a O (1)). He visto a personas hacer "in dict.keys ()" pensando que es más explícito y, por lo tanto, mejor.
Adam Parkin

2
@ AdamParkin Demostré tu comentario en mi respuesta stackoverflow.com/a/41390975/117471
Bruno Bronosky

8
@AdamParkin En Python 3, keys()es solo una vista de conjunto en un diccionario en lugar de una copia, también lo x in d.keys()es O (1). Aún así, x in des más pitónico.
Arthur Tacca

2
@ AdamParkin Interesante, no vi eso. Supongo que es porque x in d.keys()debe construir y destruir un objeto temporal, completo con la asignación de memoria que conlleva, donde x in d.keys()solo está haciendo una operación aritmética (calculando el hash) y haciendo una búsqueda. Tenga en cuenta que d.keys()solo es aproximadamente 10 veces más largo que esto, que todavía no es realmente largo. No lo he verificado, pero todavía estoy bastante seguro de que es solo O (1).
Arthur Tacca el

253

in gana sin dudas, no solo en elegancia (y no en desuso ;-) sino también en rendimiento, por ejemplo:

$ 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

Si bien la siguiente observación no siempre es cierta, notará que , por lo general , en Python, la solución más rápida es más elegante y pitónica; por eso -mtimeites TAN útil: ¡no se trata solo de ahorrar cien nanosegundos aquí y allá!)


44
Gracias por esto, verifiqué que "in some_dict" es en realidad O (1) mucho más fácil (intente aumentar el 99 para decir 1999, y encontrará que el tiempo de ejecución es casi el mismo).
Adam Parkin el

2
has_keyparece ser O (1) también.
dan-gph


42

Use dict.has_key()si (y solo si) se requiere que su código sea ejecutable por versiones de Python anteriores a la 2.3 (cuando key in dictse introdujo).


1
La actualización de WebSphere en 2013 utiliza Jython 2.1 como su lenguaje principal de secuencias de comandos. Así que desafortunadamente, esto es algo útil para tener en cuenta, cinco años después de que lo hayas notado.
ArtOfWarfare

23

Hay un ejemplo donde in realmente mata tu rendimiento.

Si lo usa inen un contenedor O (1) que solo implementa __getitem__y has_key()no __contains__lo hará, convertirá una búsqueda O (1) en una búsqueda O (N) (ya que inretrocede a una búsqueda lineal a través de__getitem__ ).

La solución es obviamente trivial:

def __contains__(self, x):
    return self.has_key(x)

66
Esta respuesta fue aplicable cuando fue publicada, pero el 99.95% de los lectores pueden ignorarla de manera segura. En la mayoría de los casos, si está trabajando con algo tan oscuro, lo sabrá.
wizzwizz4

2
Esto realmente no es un problema. has_key()es específico de los diccionarios Python 2 . in/ __contains__es la API correcta para usar; para aquellos contenedores en los que es inevitable una exploración completa, no hay ningún has_key()método de todos modos , y si hay un enfoque O (1), entonces ese será el caso de uso específico y el desarrollador debe elegir el tipo de datos correcto para el problema.
Martijn Pieters

15

has_keyes un método de diccionario, pero infuncionará en cualquier colección, e incluso cuando __contains__falte, inusará cualquier otro método para iterar la colección para averiguarlo.


1
Y también funciona en iteradores "x en xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae

1
…: Esto parece una muy mala idea: 50 operaciones en lugar de 2.
Clément

1
@ Clément En Python 3, en realidad es bastante eficiente hacer inpruebas en rangeobjetos. Sin embargo, no estoy tan seguro de su eficacia en Python 2 xrange. ;)
PM 2Ring

@ Clément no en Python 3; __contains__puede calcular trivialmente si un valor está en el rango o no.
Martijn Pieters

1
@AlexandreHuat Su tiempo incluye la sobrecarga de crear una nueva rangeinstancia cada vez. Usando una sola instancia preexistente, la prueba de "entero en el rango" es aproximadamente un 40% más rápida en mis tiempos.
MisterMiyagi

14

La solución a dict.has_key () está en desuso, use 'in' - sublime text editor 3

Aquí he tomado un ejemplo de diccionario llamado 'edades':

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"

66
Correcto, pero ya fue respondido, bienvenido a Stackoveflow, gracias por el ejemplo, ¡siempre revisa las respuestas!
igorgue

@igorgue No estoy seguro de los votos negativos para ella. Su respuesta podría ser similar a las ya respondidas, pero ella proporciona un ejemplo. ¿No es lo suficientemente digno de ser una respuesta de SO?
Akshat Agarwal

14

Ampliando las pruebas de rendimiento de Alex Martelli con los comentarios de 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

Estadísticas maravillosas, a veces implícito podría ser mejor que explícita (por lo menos en la eficiencia) ...
Varun

Gracias @varun. Me había olvidado de esta respuesta. Necesito hacer este tipo de pruebas con más frecuencia. Regularmente leo largos hilos donde la gente discute sobre The Best Way ™ para hacer cosas. Pero rara vez recuerdo lo fácil que fue obtener pruebas .
Bruno Bronosky

0

Si tienes algo como esto:

t.has_key(ew)

cámbielo a continuación para ejecutarlo en Python 3.X y superior:

key = ew
if key not in t

66
No, invertiste la prueba. t.has_key(ew)devuelve Truesi el valor de ewreferencia también es una clave en el diccionario. key not in tdevuelve Truesi el valor no está en el diccionario. Además, el key = ewalias es muy, muy redundante. La ortografía correcta es if ew in t. Que es lo que la respuesta aceptada de 8 años antes ya le dijo.
Martijn Pieters
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.