Sollte ich 'has_key ()' oder 'in' für Python-Dikte verwenden?

911

Ich frage mich, was besser zu tun ist:

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

oder:

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

Antworten:

1287

in ist definitiv pythonischer.

In der Tat has_key()wurde in Python 3.x entfernt .

Tonfa
quelle
3
Um in Python 3 zu überprüfen, ob anstelle der Schlüssel Werte vorhanden sind, versuchen Sie >>> 1 in d.values ​​()
riza
217
Ein zu vermeidendes Problem besteht darin, sicherzustellen, dass Sie Folgendes tun: "Geben Sie some_dict ein" und nicht "Geben Sie some_dict.keys () ein". Beide sind semantisch äquivalent, aber in Bezug auf die Leistung ist letzteres viel langsamer (O (n) gegenüber O (1)). Ich habe Leute gesehen, die das "in dict.keys ()" gemacht haben und dachten, es sei expliziter und daher besser.
Adam Parkin
2
@ AdamParkin Ich demonstrierte Ihren Kommentar in meiner Antwort stackoverflow.com/a/41390975/117471
Bruno Bronosky
8
@AdamParkin In Python 3 ist O (1) keys()nur eine satzartige Ansicht in ein Wörterbuch und keine Kopie x in d.keys(). Trotzdem x in dist mehr pythonisch.
Arthur Tacca
2
@AdamParkin Interessant, das habe ich nicht gesehen. Ich nehme an, es liegt daran x in d.keys(), dass ein temporäres Objekt erstellt und zerstört werden muss, einschließlich der damit verbundenen Speicherzuweisung, bei der x in d.keys()nur eine arithmetische Operation (Berechnung des Hash) und eine Suche durchgeführt werden. Beachten Sie, dass dies d.keys()nur etwa zehnmal so lang ist, was eigentlich immer noch nicht lange dauert. Ich habe nicht nachgesehen, bin mir aber ziemlich sicher, dass es nur O (1) ist.
Arthur Tacca
253

in gewinnt zweifellos, nicht nur in Eleganz (und nicht veraltet zu sein ;-), sondern auch in Leistung, zB:

$ 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

Obwohl die folgende Beobachtung nicht immer zutrifft, werden Sie feststellen, dass in Python die schnellere Lösung normalerweise eleganter und pythonischer ist. Deshalb -mtimeitist es so hilfreich - es geht nicht nur darum, hier und da hundert Nanosekunden zu sparen! -)

Alex Martelli
quelle
4
Vielen Dank dafür, dass die Überprüfung, dass "in some_dict" tatsächlich O (1) ist, viel einfacher ist (versuchen Sie, die 99 auf 1999 zu erhöhen, und Sie werden feststellen, dass die Laufzeit ungefähr gleich ist).
Adam Parkin
2
has_keyscheint auch O (1) zu sein.
Dan-Gph
96

Laut Python- Dokumenten :

has_key()wird zugunsten von abgelehnt key in d.

Nadia Alramli
quelle
1
has_key()wird jetzt in Python 3
Vadim Kotov
42

Verwenden dict.has_key()Sie diese Option, wenn (und nur wenn) Ihr Code von Python-Versionen vor 2.3 (zum Zeitpunkt key in dictder Einführung) ausgeführt werden muss.

John Machin
quelle
1
Das WebSphere-Update 2013 verwendet Jython 2.1 als Hauptskriptsprache. Dies ist leider immer noch eine nützliche Sache, fünf Jahre nachdem Sie es bemerkt haben.
ArtOfWarfare
23

Es gibt ein Beispiel, bei dem inIhre Leistung tatsächlich beeinträchtigt wird.

Wenn Sie ineinen O (1) -Container verwenden, der nur implementiert __getitem__und has_key()nicht __contains__, wird eine O (1) -Suche in eine O (N) -Suche umgewandelt (da inauf eine lineare Suche über zurückgegriffen wird __getitem__).

Fix ist offensichtlich trivial:

def __contains__(self, x):
    return self.has_key(x)
schlenk
quelle
6
Diese Antwort war gültig, als sie veröffentlicht wurde, aber 99,95% der Leser können sie ignorieren. In den meisten Fällen wissen Sie es, wenn Sie mit etwas arbeiten, das so dunkel ist.
wizzwizz4
2
Das ist wirklich kein Problem. has_key()ist spezifisch für Python 2-Wörterbücher . in/ __contains__ist die richtige API; Für Container, in denen ein vollständiger Scan unvermeidbar ist, gibt es ohnehin keine has_key()Methode. Wenn es einen O (1) -Ansatz gibt, ist dieser anwendungsfallspezifisch, und der Entwickler muss den richtigen Datentyp für das Problem auswählen.
Martijn Pieters
15

has_keyist eine Wörterbuch Methode, sondern inauf jeder Sammlung funktionieren wird, und selbst wenn __contains__, fehlt inwird eine andere Methode zu Iterierte die Sammlung verwenden , um herauszufinden.

u0b34a0f6ae
quelle
1
Und funktioniert auch mit Iteratoren "x in xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae
1
…: Das sieht nach einer sehr schlechten Idee aus: 50 Operationen statt 2.
Clément
1
@ Clément In Python 3 ist es eigentlich recht effizient, inTests an rangeObjekten durchzuführen . Ich bin mir jedoch nicht so sicher, wie effizient Python 2 xrangeist. ;)
PM 2Ring
@ Clément nicht in Python 3; __contains__kann trivial berechnen, ob ein Wert im Bereich liegt oder nicht.
Martijn Pieters
1
@AlexandreHuat Ihr Timing beinhaltet den Aufwand, rangejedes Mal eine neue Instanz zu erstellen . Bei Verwendung einer einzelnen, bereits vorhandenen Instanz ist der Test "Ganzzahl im Bereich" in meinen Timings etwa 40% schneller.
MisterMiyagi
14

Die Lösung für dict.has_key () ist veraltet. Verwenden Sie 'in' - den erhabenen Texteditor 3

Hier habe ich ein Beispiel für ein Wörterbuch mit dem Namen "Alter" genommen -

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
quelle
6
Richtig, aber es wurde bereits beantwortet, willkommen bei Stackoveflow, danke für das Beispiel, überprüfen Sie immer die Antworten!
orgorgue
@igorgue Ich bin mir nicht sicher über die Abstimmungen zu ihr. Ihre Antwort mag den bereits beantworteten ähnlich sein, aber sie liefert ein Beispiel. Ist das nicht würdig genug, um eine Antwort von SO zu sein?
Akshat Agarwal
14

Erweiterung der Leistungstests von Alex Martelli mit Adam Parkins Kommentaren ...

$ 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
quelle
Wunderbare Statistiken, manchmal implizit, sind vielleicht besser als explizit (zumindest in
Bezug
Vielen Dank, @varun. Ich hatte diese Antwort vergessen. Ich muss diese Art von Tests öfter machen. Ich lese regelmäßig lange Threads, in denen Leute über The Best Way ™ streiten, um Dinge zu tun. Aber ich erinnere mich selten, wie einfach es war, Beweise zu bekommen .
Bruno Bronosky
0

Wenn Sie so etwas haben:

t.has_key(ew)

Ändern Sie es für die Ausführung unter Python 3.X und höher wie folgt:

key = ew
if key not in t
Harshita Jhavar
quelle
6
Nein, Sie haben den Test umgekehrt. t.has_key(ew)Gibt zurück, Truewenn die Wertreferenzen ewauch ein Schlüssel im Wörterbuch sind. key not in tGibt zurück, Truewenn der Wert nicht im Wörterbuch enthalten ist. Darüber hinaus ist der key = ewAlias ​​sehr, sehr redundant. Die richtige Schreibweise ist if ew in t. Welches ist, was die akzeptierte Antwort von 8 Jahren zuvor Ihnen bereits gesagt hat.
Martijn Pieters