Da Listen veränderbar sind, müssen dict
Schlüssel (und set
Mitglieder) hashbar sein, und das Hashing veränderbarer Objekte ist eine schlechte Idee, da Hashwerte auf der Grundlage von Instanzattributen berechnet werden sollten .
In dieser Antwort werde ich einige konkrete Beispiele geben, die hoffentlich zusätzlich zu den vorhandenen Antworten einen Mehrwert bieten. Jede Einsicht gilt auch für die Elemente der set
Datenstruktur.
Beispiel 1 : Hashing eines veränderlichen Objekts, wobei der Hash-Wert auf einer veränderlichen Eigenschaft des Objekts basiert.
>>> class stupidlist(list):
... def __hash__(self):
... return len(self)
...
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True
Nach der Mutation stupid
kann es nicht mehr im Diktat gefunden werden, da sich der Hash geändert hat. Nur ein linearer Scan über die Liste der Schlüssel des Diktats findet stupid
.
Beispiel 2 : ... aber warum nicht einfach ein konstanter Hashwert?
>>> class stupidlist2(list):
... def __hash__(self):
... return id(self)
...
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>>
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False
Das ist auch keine gute Idee, da gleiche Objekte identisch hashen sollten, damit Sie sie in einem dict
oder finden können set
.
Beispiel 3 : ... ok, was ist mit konstanten Hashes über alle Instanzen hinweg?!
>>> class stupidlist3(list):
... def __hash__(self):
... return 1
...
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>>
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True
Die Dinge scheinen wie erwartet zu funktionieren, aber denken Sie darüber nach, was passiert: Wenn alle Instanzen Ihrer Klasse denselben Hash-Wert erzeugen, kommt es immer dann zu einer Hash-Kollision, wenn mehr als zwei Instanzen als Schlüssel in einem dict
oder in einem vorhanden sind set
.
Um die richtige Instanz mit my_dict[key]
oder key in my_dict
(oder item in my_set
) zu finden, müssen so viele Gleichheitsprüfungen durchgeführt werden, wie Instanzen stupidlist3
in den Schlüsseln des Diktats vorhanden sind (im schlimmsten Fall). Zu diesem Zeitpunkt ist der Zweck des Wörterbuchs - O (1) Lookup - vollständig besiegt. Dies wird in den folgenden Timings (mit IPython) demonstriert.
Einige Timings für Beispiel 3
>>> lists_list = [[i] for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>>
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Wie Sie sehen können, ist der Mitgliedschaftstest in unserem stupidlists_set
System sogar noch langsamer als ein linearer Scan lists_list
, während Sie die erwartete superschnelle Suchzeit (Faktor 500) in einem Satz ohne viele Hash-Kollisionen haben.
TL; DR: Sie können tuple(yourlist)
als dict
Schlüssel verwenden, da Tupel unveränderlich und hashbar sind.