Dovrei usare 'has_key ()' o 'in' su Python dicts?


911

Mi chiedo cosa sia meglio fare:

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

o:

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

Risposte:


1287

in è decisamente più pitonico.

Infatti has_key()è stato rimosso in Python 3.x .


3
Come aggiunta, in Python 3, per verificare l'esistenza in valori, invece delle chiavi, prova >>> 1 in d.values ​​()
riza

217
Un semi-gotcha da evitare però è assicurarsi di fare: "key in some_dict" anziché "key in some_dict.keys ()". Entrambi sono equivalenti semanticamente, ma dal punto di vista delle prestazioni quest'ultimo è molto più lento (O (n) vs O (1)). Ho visto persone fare il "in dict.keys ()" pensando che sia più esplicito e quindi migliore.
Adam Parkin,

2
@AdamParkin ho dimostrato il tuo commento nella mia risposta stackoverflow.com/a/41390975/117471
Bruno Bronosky

8
@AdamParkin In Python 3, keys()è solo una vista simile a un set in un dizionario piuttosto che una copia, così x in d.keys()è O (1). Tuttavia, x in dè più Pythonic.
Arthur Tacca,

2
@AdamParkin Interessante, non l'ho visto. Suppongo sia perché x in d.keys()deve costruire e distruggere un oggetto temporaneo, completo dell'allocazione di memoria che comporta, in cui x in d.keys()sta semplicemente facendo un'operazione aritmetica (calcolando l'hash) e facendo una ricerca. Si noti che d.keys()è solo circa 10 volte più lungo di questo, il che non è ancora lungo. Non ho controllato ma sono ancora abbastanza sicuro che sia solo O (1).
Arthur Tacca,

253

in vince a mani basse, non solo nell'eleganza (e non essendo deprecato ;-) ma anche nelle prestazioni, ad esempio:

$ 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

Mentre la seguente osservazione non è sempre vera, noterai che di solito , in Python, la soluzione più veloce è più elegante e Pythonic; ecco perché -mtimeitè così utile - non si tratta solo di salvare un centinaio di nanosecondi qua e là! -)


4
Grazie per questo, reso verificando che "in some_dict" sia in effetti O (1) molto più semplice (prova ad aumentare il 99 per dire 1999, e scoprirai che il tempo di esecuzione è più o meno lo stesso).
Adam Parkin,

2
has_keysembra essere anche O (1).
dan-gph,


42

Utilizzare dict.has_key()se (e solo se) il codice deve essere eseguibile dalle versioni di Python precedenti alla 2.3 (quando è key in dictstato introdotto).


1
L'aggiornamento WebSphere nel 2013 utilizza Jython 2.1 come linguaggio di scripting principale. Quindi questa è purtroppo ancora una cosa utile da notare, cinque anni dopo averlo notato.
ArtOfWarfare,

23

C'è un esempio in cui inuccide effettivamente la tua performance.

Se si utilizza insu un contenitore O (1) che implementa solo __getitem__e has_key()non __contains__trasformerà una ricerca O (1) in una ricerca O (N) (poiché inricade in una ricerca lineare tramite__getitem__ ).

La correzione è ovviamente banale:

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

6
Questa risposta era applicabile quando è stata pubblicata, ma il 99,95% dei lettori può tranquillamente ignorarla. Nella maggior parte dei casi, se stai lavorando con qualcosa di così oscuro, lo saprai.
wizzwizz4,

2
Questo non è davvero un problema. has_key()è specifico per i dizionari Python 2 . in/ __contains__è l'API corretta da utilizzare; per quei contenitori in cui è inevitabile una scansione completa, non esiste comunque alcun has_key()metodo e se esiste un approccio O (1), sarà specifico del caso d'uso e quindi fino allo sviluppatore scegliere il giusto tipo di dati per il problema.
Martijn Pieters

15

has_keyè un metodo di dizionario, ma infunzionerà su qualsiasi raccolta e anche quando __contains__manca, inutilizzerà qualsiasi altro metodo per iterare la raccolta per scoprirlo.


1
E funziona anche su iteratori "x in xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae,

1
...: Sembra una pessima idea: 50 operazioni invece di 2.
Clément,

1
@ Clément In Python 3, in realtà è abbastanza efficiente fare intest sugli rangeoggetti. Non sono così sicuro della sua efficienza su Python 2 xrange, però. ;)
PM 2Ring

@ Clément non in Python 3; __contains__può banalmente calcolare se un valore è compreso nell'intervallo o meno.
Martijn Pieters

1
@AlexandreHuat Il tuo tempismo include il sovraccarico di creare una nuova rangeistanza ogni volta. Utilizzando una singola istanza preesistente , il test "integer in range" è circa il 40% più veloce nei miei tempi.
MisterMiyagi,

14

La soluzione a dict.has_key () è obsoleta, usa 'in' - editor di testo sublime 3

Qui ho preso un esempio di dizionario chiamato 'age' -

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"

6
Corretto, ma ha già ricevuto risposta, benvenuto su Stackoveflow, grazie per l'esempio, controlla sempre le risposte!
igorgue,

@Gorgue non sono sicuro dei voti negativi per lei. La sua risposta potrebbe essere simile a quelle già risposte, ma lei fornisce un esempio. Non è abbastanza degno di essere una risposta di SO?
Akshat Agarwal,

14

Ampliamento dei test prestazionali di Alex Martelli con i commenti di 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

Statistiche meravigliose, a volte implicite potrebbero essere migliori di quelle esplicite (almeno in termini di efficienza) ...
Varun,

Grazie, @varun. Mi ero dimenticato di questa risposta. Ho bisogno di fare questo tipo di test più spesso. Leggo regolarmente lunghi thread in cui le persone discutono di The Best Way ™ per fare le cose. Ma ricordo raramente quanto fosse facile ottenere prove .
Bruno Bronosky,

0

Se hai qualcosa del genere:

t.has_key(ew)

cambiarlo in basso per l'esecuzione su Python 3.X e versioni successive:

key = ew
if key not in t

6
No, hai invertito il test. t.has_key(ew)ritorna Truese anche i ewriferimenti ai valori sono una chiave nel dizionario. key not in tritorna Truese il valore non è nel dizionario. Inoltre, l' key = ewalias è molto, molto ridondante. L'ortografia corretta è if ew in t. È ciò che ti ha già detto la risposta accettata di 8 anni prima.
Martijn Pieters
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.