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

Odpowiedzi:


1286

in jest zdecydowanie bardziej pytoniczny.

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


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! -)


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


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).


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)

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ć.


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"

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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.