Czy powinienem używać „has_key ()” lub „in” w słownikach Python?

910

Zastanawiam się, co lepiej zrobić:

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

lub:

d = {'a': 1, 'b': 2}
d.has_key('a')
True
igorgue
źródło

Odpowiedzi:

1286

in jest zdecydowanie bardziej pytoniczny.

W rzeczywistości has_key()usunięto Python 3.x .

tonfa
źródło
3
Jako dodatek, w Pythonie 3, w celu sprawdzenia istnienia w wartości, zamiast kluczy, spróbuj >>> 1 w d.values ()
Riza
217
Jedną z pół-gotcha, której należy unikać, jest upewnienie się, że zrobisz: „wprowadź some_dict” zamiast „wpisz some_dict.keys ()”. Oba są równoważne semantycznie, ale pod względem wydajności ten drugi jest znacznie wolniejszy (O (n) vs O (1)). Widziałem, jak ludzie robią „in dict.keys ()”, myśląc, że jest to bardziej wyraźne i dlatego lepsze.
Adam Parkin,
2
@AdamParkin W komentarzu zademonstrowałem twój komentarz stackoverflow.com/a/41390975/117471
Bruno Bronosky
8
@AdamParkin W Pythonie 3 keys()jest tylko zestawem widoków na słownik, a nie na kopie, podobnie jak x in d.keys()O (1). Mimo x in dto jest bardziej Python.
Arthur Tacca
2
@AdamParkin Interesujące, nie widziałem tego. Przypuszczam, że dzieje się tak, ponieważ x in d.keys()musi skonstruować i zniszczyć obiekt tymczasowy, wraz z przydziałem pamięci, który pociąga za sobą, w którym x in d.keys()po prostu wykonuje operację arytmetyczną (obliczanie skrótu) i wykonuje wyszukiwanie. Zauważ, że d.keys()jest to tylko około 10 razy dłużej niż to, co tak naprawdę nie jest długie. Nie sprawdziłem, ale wciąż jestem pewien, że to tylko O ​​(1).
Arthur Tacca,
253

in wygrywa wręcz, nie tylko w elegancji (i nie jest przestarzały ;-), ale także w wydajności, np .:

$ 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

Chociaż poniższe obserwacje nie zawsze są prawdziwe, zauważysz, że zwykle w Pythonie szybsze rozwiązanie jest bardziej eleganckie i Pythoniczne; dlatego -mtimeitjest tak pomocny - nie chodzi tylko o zaoszczędzenie setek nanosekund tu i tam! -)

Alex Martelli
źródło
4
Dzięki za to sprawiłem, że weryfikacja, że ​​„in some_dict” jest w rzeczywistości O (1) o wiele łatwiejsza (spróbuj zwiększyć 99 do 1999, a przekonasz się, że środowisko wykonawcze jest mniej więcej takie samo).
Adam Parkin,
2
has_keywydaje się być również O (1).
dan-gph
96

Według dokumentów Pythona :

has_key()jest przestarzałe na korzyść key in d.

Nadia Alramli
źródło
1
has_key()jest teraz usunięty w Python 3
Vadim Kotov
42

Użyj dict.has_key()jeśli (i tylko jeśli) twój kod musi być uruchamiany przez wersje Pythona wcześniejsze niż 2.3 (kiedy key in dictzostał wprowadzony).

John Machin
źródło
1
Aktualizacja WebSphere w 2013 r. Używa Jython 2.1 jako głównego języka skryptowego. Jest to niestety nadal użyteczna rzecz do odnotowania, pięć lat po tym, jak to zauważyłeś.
ArtOfWarfare
23

Jest jeden przykład, w którym infaktycznie zabija twoją wydajność.

Jeśli używasz inw czasie O (1) pojemnik, że tylko narzędzia __getitem__i has_key()ale nie __contains__można włączyć O (1) szukaj w O (N) wyszukiwania (jak inwraca do wyszukiwania poprzez liniowy __getitem__).

Naprawienie jest oczywiście trywialne:

def __contains__(self, x):
    return self.has_key(x)
Schlenk
źródło
6
Ta odpowiedź miała zastosowanie, gdy została opublikowana, ale 99,95% czytelników może ją bezpiecznie zignorować. W większości przypadków, jeśli pracujesz z czymś tak niejasnym, będziesz o tym wiedział.
wizzwizz4
2
To naprawdę nie jest problem. has_key()jest specyficzny dla słowników Python 2 . in/ __contains__to właściwy interfejs API do użycia; dla tych pojemników w których pełne skanowanie jest nieuniknione, nie jest has_key()metoda i tak , a jeśli nie jest O (1) Podejście wtedy będzie use-case specyficzny i tak aż do dewelopera, aby wybrać odpowiedni typ danych dla tego problemu.
Martijn Pieters
15

has_keyjest metodą słownikową, ale inbędzie działać na dowolnej kolekcji, a nawet gdy jej __contains__brakuje, inużyje dowolnej innej metody, aby iterować kolekcję, aby się dowiedzieć.

u0b34a0f6ae
źródło
1
Działa także na iteratorach „x w xrange (90, 200) <=> 90 <= x <200”
u0b34a0f6ae
1
…: Wygląda to na bardzo zły pomysł: 50 operacji zamiast 2.
Clément
1
@ Clément W Pythonie 3 przeprowadzanie intestów na rangeobiektach jest dość wydajne . Nie jestem jednak pewien jego wydajności w Pythonie 2 xrange. ;)
PM 2Ring
@ Clément nie w Pythonie 3; __contains__Potrafi w prosty sposób obliczyć, czy wartość mieści się w zakresie, czy nie.
Martijn Pieters
1
@AlexandreHuat Twoje wyczucie czasu obejmuje koszty związane z tworzeniem nowej rangeinstancji za każdym razem. Używając jednej, istniejącej wcześniej instancji, test „liczby całkowitej w zakresie” jest o około 40% szybszy w moich momentach.
MisterMiyagi,
14

Rozwiązanie dict.has_key () jest przestarzałe, użyj „in” - wysublimowanego edytora tekstu 3

Tutaj wziąłem przykład słownika o nazwie „wiek” -

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
źródło
6
Zgadza się, ale już otrzymano odpowiedź, witaj w Stackoveflow, dzięki za przykład, zawsze jednak sprawdzaj odpowiedzi!
igorgue
@igorgue nie jestem pewien co do jej opinii. Jej odpowiedź może być podobna do tych, na które już odpowiedziano, ale stanowi przykład. Czy to nie jest na tyle godne odpowiedzi SO?
Akshat Agarwal
14

Rozszerzanie testów wydajności Alexa Martellego o komentarze Adama Parkina ...

$ 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
źródło
Wspaniałe statystyki, czasem dorozumiane, mogą być lepsze niż jawne (przynajmniej pod względem wydajności) ...
varun 30.03.18
Dziękuję, @varun. Zapomniałem o tej odpowiedzi. Muszę częściej przeprowadzać tego rodzaju testy. Regularnie czytam długie wątki, w których ludzie kłócą się o The Best Way ™ . Ale rzadko pamiętam, jak łatwo było uzyskać dowód .
Bruno Bronosky
0

Jeśli masz coś takiego:

t.has_key(ew)

zmień go poniżej, aby uruchomić na Pythonie 3.X i nowszych:

key = ew
if key not in t
Harshita Jhavar
źródło
6
Nie, odwróciłeś test. t.has_key(ew)zwraca, Truejeśli ewodwołania do wartości są również kluczem w słowniku. key not in tzwraca, Truejeśli wartości nie ma w słowniku. Co więcej, key = ewalias jest bardzo, bardzo zbędny. Prawidłowa pisownia to if ew in t. Tak powiedziała już zaakceptowana odpowiedź sprzed 8 lat.
Martijn Pieters