Hashable objects

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
hendrikS
User
Beiträge: 420
Registriert: Mittwoch 24. Dezember 2008, 22:44
Wohnort: Leipzig

[Edit (Leonidas): Von "Kleiner Bericht zum Umstieg von Sprache xy auf Python" abgetrennt.]
mechanicalStore hat geschrieben:Wozu sind Tupel nützlich?
Die Frage stelle ich mir auch ständig. Die Abgrenzung zu Listen ist einfach zu schwach. Im Grund ist es nur die Eigenschaft readonly. Aber das ist oft eher hinterlich als vorteilhaft. Auch stehen viele Funktionen, die ich auf Listen anwenden kann für Tuples nicht zur Verfügung, so daß ich am Ende doch meistens auf list() zurückgreife.
Ich finde die Haskell'sche Abgrenzung von Listen und Tuples klarer. Ach wenn ich bei Listen keine Typen mischen kann.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

hendrikS hat geschrieben:
mechanicalStore hat geschrieben:Wozu sind Tupel nützlich?
Die Frage stelle ich mir auch ständig. Die Abgrenzung zu Listen ist einfach zu schwach. Im Grund ist es nur die Eigenschaft readonly. Aber das ist oft eher hinterlich als vorteilhaft.
Ich habe auch anfangs nicht recht verstanden, wozu es Tupel UND Listen braucht. Vor einiger Zeit (ich schätze > 1 Jahr) gab es mal einen interessanten Thread dazu im Forum, wo einige erläutert haben, wie sie Tupel bzw. Listen einsetzen. Mittlerweile schätze ich es aber, Tupel und Listen verfügbar zu haben. In der Praxis nutze ich Tupel und Listen so wie in Haskell: Ein Tupel bildet quasi eine Einheit aus verschiedenen Elementen und bildet so ein neues Element. Listen verwende ich in der Regel als typgleiche Listen.
hendrikS hat geschrieben:Auch stehen viele Funktionen, die ich auf Listen anwenden kann für Tuples nicht zur Verfügung, so daß ich am Ende doch meistens auf list() zurückgreife.
Zumindest seit Python 3 kann man das so nicht stehen lassen. Alle Methoden, die sich nicht wegen des unveränderlichen Charakters der Tupel ohnehin verbieten, gelten jetzt auch für Tupel. Für Python 2.x war das in der Tat lästig, weil index() und count() für Tupel nicht definiert waren.
Benutzeravatar
hendrikS
User
Beiträge: 420
Registriert: Mittwoch 24. Dezember 2008, 22:44
Wohnort: Leipzig

numerix hat geschrieben:In der Praxis nutze ich Tupel und Listen so wie in Haskell: Ein Tupel bildet quasi eine Einheit aus verschiedenen Elementen und bildet so ein neues Element. Listen verwende ich in der Regel als typgleiche Listen.
Das halte ich ebenso.
numerix hat geschrieben:Zumindest seit Python 3 kann man das so nicht stehen lassen. Alle Methoden, die sich nicht wegen des unveränderlichen Charakters der Tupel ohnehin verbieten, gelten jetzt auch für Tupel. Für Python 2.x war das in der Tat lästig, weil index() und count() für Tupel nicht definiert waren.
Oh, mal was sinnvolles in Py3. Leider werde ich trotzdem das Gefühl nicht los, daß man bei der aktuellen Implementierung Tuples eigentlich in den Skat drücken könnte.
BlackJack

Nochwas zum Sinn von Tupeln: Versucht mal Listen als Schlüssel in Dictionaries zu verwenden. ;-) Also ich könnte da nicht drauf verzichten.
Benutzeravatar
hendrikS
User
Beiträge: 420
Registriert: Mittwoch 24. Dezember 2008, 22:44
Wohnort: Leipzig

BlackJack hat geschrieben:Nochwas zum Sinn von Tupeln: Versucht mal Listen als Schlüssel in Dictionaries zu verwenden. ;-) Also ich könnte da nicht drauf verzichten.
Bekomme TypeError: list objects are unhashable

Kann das jemand erklären?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Darauf wollte BlackJack ja hinaus: Listen sind veraenderbar und deshalb nicht hashbar (ergo nicht als Key benutzbar).
Die Idee sollte einem allerdings auch komisch vorkommen, da einen das auf Objektidentitaet festnageln wuerde und nicht auf den (gehashten) Inhalt.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

hendrikS hat geschrieben:
BlackJack hat geschrieben:Nochwas zum Sinn von Tupeln: Versucht mal Listen als Schlüssel in Dictionaries zu verwenden. ;-) Also ich könnte da nicht drauf verzichten.
Bekomme TypeError: list objects are unhashable

Kann das jemand erklären?
Schon den ";)" in BackJacks Post gesehen? :)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
hendrikS
User
Beiträge: 420
Registriert: Mittwoch 24. Dezember 2008, 22:44
Wohnort: Leipzig

cofi hat geschrieben:Listen sind veraenderbar und deshalb nicht hashbar
Verstehe ich irgendwie nicht warum das so sein sollte.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

hendrikS hat geschrieben:
cofi hat geschrieben:Listen sind veraenderbar und deshalb nicht hashbar
Verstehe ich irgendwie nicht warum das so sein sollte.
Naja, der Hashwert leitet sich vom Inhalt (dem Wert) des Objektes ab, somit wäre der Hashwert der Liste immer anders, je nachdem was mit der Liste gemacht wurde. Damit könnte man nicht mehr auf die Items in einem Dict zugreifen, weil sich der hashwert der Lsite verändert hat. Das will man eben ausschließen indem in Python Listen "unhashable" sind.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich finde ja, man sollte solche Geschichten nicht nur von der Funktionalität her betrachten, sondern auch in Hinblick auf's Design der API. Nehmen wir z.B. mal eine Funktion zur Rückgabe von den RGB-Werten einer bestimmten Farbe: Wieso sollte man da eine Liste zurückgeben wollen, wenn doch klar ist, dass es immer 3 Werte sind, die bestimmte Eigenschaften beschreiben, welche auch immer zu dritt auftreten, d.h. unveränderlich sind? Ich finde es eigentlich schwieriger, zwischen Tupel und Klasse (`color.red`) zu unterscheiden. Oft zeigt sich aber, dass etwas, das ich mir im Kopf als Klasse vorgestellt habe (eine bestimmte Art mit ganz bestimmten Eigenschaften) beim Programmieren dann irgendwie doch nach zuviel Overhead aussieht und ich entscheide mich für Tupel.

Ähnlich verhält es sich ja auch mit `xrange()` vs. `range()`: Geschwindigkeitsmäßig macht es nicht wirklich einen Unterschied, ob ich mit `range(5)` den Umweg über die Liste nehme, um entsprechend viele Durchläufe in einer Schleife zu machen. Aber ich würde ihn hier nun mal nicht gehen, weil ich ja im Prinzip keine Liste brauche und sie am Ende des Iterierens ohnehin wegschmeißen würde. Und genau das kann ich prima mit `xrange(5)` ausdrücken. Daher finde ich es auch gut, dass Python 3.x vermehrt Iteratoren zurückgibt. Denn es deckt in den meisten Fällen genau das ab, was man möchte, ohne dass man ständig die [mod]itertools[/mod] bemühen muss, sofern man die gerade beschriebene Konsequenz in Sachen Typwahl einhalten möchte.
lunar

@hendrikS: Deswegen:

Code: Alles auswählen

class Foo(object):
    def __init__(self, x):
        self.value = x
    def __hash__(self):
        return hash(self.value)
    def __eq__(self, other):
        return self.value == other.value
    def __ne__(self, other):
         return self.value != other.value

f = Foo('hallo')
d = {f: 'welt'}
f.value = 'tschüss'
d[f] # <-- ergibt KeyError, weil sich der Hash von f geändert hat
Bzw. mit einer Liste:

Code: Alles auswählen

class mylist(list):
    def __hash__(self):
        return hash(tuple(self))

l = mylist(range(10))
d = {l: 'spam'}
l.append(10)
d[l] # <-- KeyError
l2 = mylist(range(11))
d[l2] # <-- ebenfalls KeyError.
Es gibt schlichtweg keine Möglichkeit mehr, auf 'spam' zuzugreifen, sofern nicht "l" wieder in den Ausgangszustand versetzt wird.

Der Grund ist, dass 'spam' unter dem Hash von l im Dicitionary liegt, aber eigentlich unter dem Hash von l2 gespeichert sein müsste. Greift man mit dem Hash von l darauf zu, gibt es einen KeyError, weil der gespeicherte und der übergebene Schlüssel nicht wertgleich sind, greift man über l2 darauf zu, stimmt der Hash nicht.

Das dahinter liegende Problem ist, dass man bei einer Veränderung des Werts des Schlüssels den dazugehören Wert im Dictionary entsprechend seines neuen Hashs verschieben müsste. Dazu aber müsste man jede Attributveränderung des Schlüssels abfangen, was nicht vertretbar ist.
Zuletzt geändert von lunar am Sonntag 3. Januar 2010, 16:07, insgesamt 3-mal geändert.
Benutzeravatar
hendrikS
User
Beiträge: 420
Registriert: Mittwoch 24. Dezember 2008, 22:44
Wohnort: Leipzig

Leonidas hat geschrieben:Naja, der Hashwert leitet sich vom Inhalt (dem Wert) des Objektes ab, somit wäre der Hashwert der Liste immer anders, je nachdem was mit der Liste gemacht wurde.
Klar. Und wenn es diesen Key nicht gibt, würde es einen KeyError geben.
Wo ist das Poblem?
Erklärt nicht warum Listen unhashable sind. Merkwürdig?
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

@hendrikS:

Code: Alles auswählen

>>> class List(list):
...     def __hash__(self):
...         return hash(tuple(self))
...
>>> key = List()
>>> D = {key: 42}
>>> D[key]
42
>>> key.append('Oh noes')
>>> D[key]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
KeyError: ['Oh noes']
>>> D[D.keys()[0]]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
KeyError: ['Oh noes']
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

hendrikS hat geschrieben:Erklärt nicht warum Listen unhashable sind. Merkwürdig?
Weil es so implementiert wurde. Alle built-in-Typen bilden ihren Hashwert über ihren Inhalt, um konsistent zu bleiben wollte man bei Listen vermutlich nicht anders verfahren. Ein Objekt ist genau dann „hashable“, wenn sich der Hash nie ändert. Da Listen und somit ihr Hash veränderbar sind, wären Listen also „unhashable“.

Eine Alternative wäre gewesen den Hash über die Objektidentität abzuleiten, das ist auch überhaupt nicht abwegig und passiert automatisch bei allen eigenen Klassen. Allerdings müssen Objekte die „gleich“(per obj1 == obj2) sind, auch den selben Hashwert haben. Um Listen also hashbar zu machen müsste man den Vergleich auf Gleichheit auch über die Objektidentität laufen lassen, was etwas ist, was man nun wirklich nicht will. Schließlich sollten Listen die den gleichen Inhalt haben, aber nicht identisch sind „gleich“ sein. Also lieber „unhashable“ Listen.
Benutzeravatar
hendrikS
User
Beiträge: 420
Registriert: Mittwoch 24. Dezember 2008, 22:44
Wohnort: Leipzig

Darii hat geschrieben:Ein Objekt ist genau dann „hashable“, wenn sich der Hash nie ändert.
Wenn das per Definition so ist. OK. Die Python Entwickler werden sich schon was dabei gedacht haben. Kann zumindest nicht schaden zu wissen.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Code: Alles auswählen

>>> hash(-1) == hash(-2)
True
Und ja, ich weiß, dass das ein Sonderfall ist... ;)
lunar

Ja und? Der Hash eines Objekts ist nun mal nicht eindeutig. Aber was hat das jetzt mit der Diskussion zu tun?
Antworten