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

Antworten:


1287

in ist definitiv pythonischer.

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


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


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


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.


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)

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.


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"

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

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

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.