Warum kann ich in Python keine Liste als Diktatschlüssel verwenden?


100

Ich bin etwas verwirrt darüber, was als Schlüssel für ein Python-Diktat verwendet werden kann / nicht.

dicked = {}
dicked[None] = 'foo'     # None ok
dicked[(1,3)] = 'baz'    # tuple ok
import sys
dicked[sys] = 'bar'      # wow, even a module is ok !
dicked[(1,[3])] = 'qux'  # oops, not allowed

Ein Tupel ist also ein unveränderlicher Typ, aber wenn ich eine Liste darin verstecke, kann es kein Schlüssel sein. Könnte ich eine Liste nicht genauso einfach in einem Modul verstecken?

Ich hatte eine vage Vorstellung davon, dass der Schlüssel "hashbar" sein muss, aber ich gebe nur meine eigene Unkenntnis über die technischen Details zu; Ich weiß nicht, was hier wirklich los ist. Was würde schief gehen, wenn Sie versuchen würden, Listen als Schlüssel zu verwenden, wobei der Hash beispielsweise deren Speicherort ist?


1
Hier ist eine gute Diskussion: stackoverflow.com/questions/2671211/…
Hernan

49
Ich habe ein Kichern aus Ihrem Variablennamen.
Kindall

Antworten:


33

Es gibt einen guten Artikel zu diesem Thema im Python-Wiki: Warum Listen keine Wörterbuchschlüssel sein können . Wie dort erklärt:

Was würde schief gehen, wenn Sie versuchen würden, Listen als Schlüssel zu verwenden, wobei der Hash beispielsweise deren Speicherort ist?

Dies kann durchgeführt werden, ohne die Anforderungen wirklich zu verletzen, führt jedoch zu unerwartetem Verhalten. Listen werden im Allgemeinen so behandelt, als ob ihr Wert aus den Werten ihres Inhalts abgeleitet wurde, beispielsweise bei der Überprüfung der (Un-) Gleichheit. Viele würden - verständlicherweise - erwarten, dass Sie jede Liste verwenden können [1, 2], um denselben Schlüssel zu erhalten, bei dem Sie genau dasselbe Listenobjekt behalten müssten . Die Suche nach Werten wird jedoch unterbrochen, sobald eine als Schlüssel verwendete Liste geändert wird. Für die Suche nach Identität müssen Sie genau dieselbe Liste verwenden - was für keine andere allgemeine Listenoperation erforderlich ist (zumindest keine, die mir einfällt) ).

Andere Objekte wie Module und objectmachen sowieso viel mehr aus ihrer Objektidentität (wann haben Sie das letzte Mal zwei verschiedene Modulobjekte aufgerufen sys?) Und werden damit trotzdem verglichen. Daher ist es weniger überraschend - oder sogar zu erwarten -, dass sie, wenn sie als Diktatschlüssel verwendet werden, auch in diesem Fall nach Identität verglichen werden.


30

Warum kann ich in Python keine Liste als Diktatschlüssel verwenden?

>>> d = {repr([1,2,3]): 'value'}
{'[1, 2, 3]': 'value'}

(für alle, die über diese Frage stolpern und nach einem Weg suchen, sie zu umgehen)

Wie von anderen hier erklärt, können Sie in der Tat nicht. Sie können jedoch stattdessen die Zeichenfolgendarstellung verwenden, wenn Sie Ihre Liste wirklich verwenden möchten.


5
Entschuldigung, ich verstehe Ihren Standpunkt nicht wirklich. Es ist nicht anders, als String-Literale als Schlüssel zu verwenden.
wim

11
Wahr; Ich habe gerade so viele Antworten gesehen, die tatsächlich erklären, warum man Listen nicht in Bezug auf "Schlüssel muss hashbar sein" verwenden kann, was so wahr ist, dass ich einen Weg vorschlagen wollte, um es zu umgehen, nur für den Fall, dass jemand (neu) danach sucht ...
Remi

5
Warum nicht einfach die Liste in ein Tupel konvertieren? Warum in einen String konvertieren? Wenn Sie ein Tupel verwenden, funktioniert es ordnungsgemäß mit Klassen, die über eine benutzerdefinierte Vergleichsmethode verfügen __eq__. Wenn Sie sie jedoch in Zeichenfolgen konvertieren, wird alles anhand der Zeichenfolgendarstellung verglichen.
Aran-Fey

guter Punkt @ Aran-Fey. Stellen Sie einfach sicher, dass jedes Element im Tupel selbst hashbar ist. zB Tupel ([[1,2], [2,3]]) als Schlüssel funktioniert nicht, da die Elemente des Tupels noch Listen sind.
Remi

17

Gerade gefunden, können Sie List in Tupel ändern und dann als Schlüssel verwenden.

d = {tuple([1,2,3]): 'value'}

15

Das Problem ist, dass Tupel unveränderlich sind und Listen nicht. Folgendes berücksichtigen

d = {}
li = [1,2,3]
d[li] = 5
li.append(4)

Was soll d[li]zurückkehren? Ist es die gleiche Liste? Wie wäre es mit d[[1,2,3]]? Es hat die gleichen Werte, aber ist eine andere Liste?

Letztendlich gibt es keine zufriedenstellende Antwort. Wenn der einzige Schlüssel, der funktioniert, beispielsweise der Originalschlüssel ist, können Sie nie wieder auf den Wert zugreifen, wenn Sie keinen Verweis auf diesen Schlüssel haben. Mit jedem anderen zulässigen Schlüssel können Sie einen Schlüssel ohne Verweis auf das Original erstellen.

Wenn meine beiden Vorschläge funktionieren, haben Sie sehr unterschiedliche Schlüssel, die denselben Wert zurückgeben, was mehr als überraschend ist. Wenn nur der ursprüngliche Inhalt funktioniert, wird Ihr Schlüssel schnell fehlerhaft, da Listen geändert werden müssen.


Ja, es ist dieselbe Liste, also würde ich erwarten d[li], dass sie bleibt. 5. d[[1,2,3]]würde sich auf ein anderes Listenobjekt als Schlüssel beziehen, also wäre es ein KeyError. Ich sehe noch kein wirkliches Problem. Abgesehen davon, dass das Sammeln von Müll durch einen Schlüssel dazu führen kann, dass einige der Diktatwerte nicht mehr zugänglich sind. Aber das ist ein praktisches Problem, kein logisches Problem.
wim

@wim: d[list(li)]Ein KeyError zu sein ist Teil des Problems. In fast jedem anderen Anwendungsfall , liwürde von einer neuen Liste mit identischem Inhalt nicht zu unterscheiden sein. Es funktioniert, ist aber für viele nicht intuitiv. Und wann mussten Sie das letzte Mal wirklich eine Liste als Diktatschlüssel verwenden? Der einzige Anwendungsfall, den ich mir vorstellen kann, ist, wenn Sie sowieso alles nach Identität durchsuchen. In diesem Fall sollten Sie dies einfach tun, anstatt sich auf identitätsbasiert zu verlassen __hash__und __eq__auf dieser zu basieren.

@delnan Ist das Problem einfach, dass es wegen solcher Komplikationen kein sehr nützliches Diktat wäre? oder gibt es einen Grund, warum es tatsächlich ein Diktat brechen könnte?
wim

1
@wim: Letzteres. Wie in meiner Antwort angegeben, werden die Anforderungen an Diktatschlüssel nicht wirklich verletzt, aber es werden wahrscheinlich mehr Probleme auftreten, als es löst.

1
@delnan - du wolltest 'der erstere' sagen
Jason

9

Hier ist eine Antwort http://wiki.python.org/moin/DictionaryKeys

Was würde schief gehen, wenn Sie versuchen würden, Listen als Schlüssel zu verwenden, wobei der Hash beispielsweise deren Speicherort ist?

Das Nachschlagen verschiedener Listen mit demselben Inhalt würde zu unterschiedlichen Ergebnissen führen, obwohl ein Vergleich von Listen mit demselben Inhalt sie als gleichwertig anzeigen würde.

Was ist mit der Verwendung eines Listenliteral in einer Wörterbuchsuche?


3

Ihre Markise finden Sie hier:

Warum Listen keine Wörterbuchschlüssel sein können

Python-Neulinge fragen sich oft, warum, obwohl die Sprache sowohl ein Tupel als auch einen Listentyp enthält, Tupel als Wörterbuchschlüssel verwendet werden können, Listen jedoch nicht. Dies war eine bewusste Entwurfsentscheidung und kann am besten erklärt werden, indem man zuerst versteht, wie Python-Wörterbücher funktionieren.

Quelle und weitere Informationen: http://wiki.python.org/moin/DictionaryKeys


3

Da Listen veränderbar sind, müssen dictSchlüssel (und setMitglieder) 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 setDatenstruktur.

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 stupidkann 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 dictoder 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 dictoder 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 stupidlist3in 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_setSystem 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 dictSchlüssel verwenden, da Tupel unveränderlich und hashbar sind.


>>> x = (1,2,3321321321321,) >>> id (x) 139936535758888 >>> z = (1,2,3321321321321,) >>> id (z) 139936535760544 >>> id ((1, 2,3321321321321,)) 139936535810768 Diese 3 haben die gleichen Tupelwerte, aber unterschiedliche IDs. Ein Wörterbuch mit Schlüssel x hat also keinen Wert für Schlüssel z?
Ashwani

@ Ashwani hast du es ausprobiert?
Timgeb

Ja, es funktioniert wie erwartet. Mein Zweifel ist, dass alle Tupel mit denselben Werten unterschiedliche IDs haben. Auf welcher Basis wird dieser Hash berechnet?
Ashwani

@ Ashwani Der Hash von xund zist der gleiche. Wenn etwas darüber unklar ist, öffnen Sie bitte eine neue Frage.
Timgeb

1
@ Ashwani hash(x)und hash(z).
Timgeb

1

Die einfache Antwort auf Ihre Frage lautet, dass die Klassenliste nicht den Methoden- Hash implementiert, der für ein Objekt erforderlich ist, das als Schlüssel in einem Wörterbuch verwendet werden soll. Der Grund, warum Hash nicht so implementiert wird wie beispielsweise die Tupelklasse (basierend auf dem Inhalt des Containers), liegt darin, dass eine Liste veränderbar ist, sodass für die Bearbeitung der Liste der Hash neu berechnet werden muss, was bedeuten kann, dass die Liste in befindet sich jetzt im falschen Eimer in der Untergrund-Hash-Tabelle. Da Sie ein Tupel (unveränderlich) nicht ändern können, tritt dieses Problem nicht auf.

Nebenbei bemerkt basiert die tatsächliche Implementierung der Diktobjektsuche auf dem Algorithmus D von Knuth Vol. 1. 3, Sec. 6.4. Wenn Sie dieses Buch zur Verfügung haben, ist es möglicherweise eine lohnende Lektüre. Wenn Sie wirklich, wirklich interessiert sind, können Sie hier einen Blick auf die Entwicklerkommentare zur tatsächlichen Implementierung von dictobject werfen. Es wird sehr detailliert beschrieben, wie es genau funktioniert. Es gibt auch eine Python-Vorlesung über die Implementierung von Wörterbüchern, an denen Sie interessiert sein könnten. Sie gehen die Definition eines Schlüssels durch und was ein Hash in den ersten Minuten ist.


-1

Gemäß der Python 2.7.2-Dokumentation:

Ein Objekt ist hashbar, wenn es einen Hashwert hat, der sich während seiner Lebensdauer nie ändert (es benötigt eine hash () -Methode) und mit anderen Objekten verglichen werden kann (es benötigt eine eq () - oder cmp () -Methode). Hashbare Objekte, die gleich sind, müssen denselben Hashwert haben.

Durch die Hashability kann ein Objekt als Wörterbuchschlüssel und als festgelegtes Element verwendet werden, da diese Datenstrukturen den Hashwert intern verwenden.

Alle unveränderlichen integrierten Objekte von Python sind hashbar, während keine veränderbaren Container (wie Listen oder Wörterbücher) vorhanden sind. Objekte, die Instanzen benutzerdefinierter Klassen sind, können standardmäßig gehasht werden. Sie alle vergleichen sich ungleich und ihr Hash-Wert ist ihre ID ().

Ein Tupel ist in dem Sinne unveränderlich, dass Sie seine Elemente nicht hinzufügen, entfernen oder ersetzen können, aber die Elemente selbst können veränderlich sein. Der Hash-Wert von List hängt von den Hash-Werten seiner Elemente ab und ändert sich daher, wenn Sie die Elemente ändern.

Die Verwendung von IDs für Listen-Hashes würde bedeuten, dass alle Listen unterschiedlich verglichen werden, was überraschend und unpraktisch wäre.


1
Das beantwortet die Frage nicht, oder? hash = idbricht die Invariante am Ende des ersten Absatzes nicht, die Frage ist, warum es nicht so gemacht wird.

@delnan: Ich habe den letzten Absatz hinzugefügt, um dies zu verdeutlichen.
Nicola Musatti

-1

Ein Wörterbuch ist eine HashMap, in der die Zuordnung Ihrer Schlüssel, der in einen gehashten neuen Schlüssel konvertierte Wert und die Wertzuordnung gespeichert werden.

so etwas wie (Pseudo-Code):

{key : val}  
hash(key) = val

Wenn Sie sich fragen, welche Optionen verfügbar sind, die als Schlüssel für Ihr Wörterbuch verwendet werden können. Dann

Alles, was hashbar ist (kann in Hash konvertiert werden und einen statischen Wert haben, dh unveränderlich, um einen Hash-Schlüssel wie oben angegeben zu erstellen ), ist zulässig, aber da Listen- oder Set-Objekte unterwegs variiert werden können, sollte auch Hash (Schlüssel) benötigt werden zu variieren, nur um mit Ihrer Liste oder Ihrem Set synchron zu sein.

Du kannst es versuchen :

hash(<your key here>)

Wenn es gut funktioniert, kann es als Schlüssel für Ihr Wörterbuch verwendet oder in etwas Hashbares konvertiert werden.


Zusamenfassend :

  1. Konvertieren Sie diese Liste in tuple(<your list>).
  2. Konvertieren Sie diese Liste in str(<your list>).

-1

dictSchlüssel müssen hashbar sein. Listen sind veränderlich und bieten keine gültige Hash- Methode.

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.