Seite 1 von 1

Hashable objects

Verfasst: Sonntag 3. Januar 2010, 11:06
von hendrikS
[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.

Re: Kleiner Bericht zum Umstieg von Sprache xy auf Python

Verfasst: Sonntag 3. Januar 2010, 12:00
von numerix
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.

Re: Kleiner Bericht zum Umstieg von Sprache xy auf Python

Verfasst: Sonntag 3. Januar 2010, 12:17
von hendrikS
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.

Verfasst: Sonntag 3. Januar 2010, 14:19
von BlackJack
Nochwas zum Sinn von Tupeln: Versucht mal Listen als Schlüssel in Dictionaries zu verwenden. ;-) Also ich könnte da nicht drauf verzichten.

Verfasst: Sonntag 3. Januar 2010, 14:42
von hendrikS
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?

Verfasst: Sonntag 3. Januar 2010, 14:53
von cofi
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.

Verfasst: Sonntag 3. Januar 2010, 14:53
von Leonidas
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? :)

Verfasst: Sonntag 3. Januar 2010, 15:32
von hendrikS
cofi hat geschrieben:Listen sind veraenderbar und deshalb nicht hashbar
Verstehe ich irgendwie nicht warum das so sein sollte.

Verfasst: Sonntag 3. Januar 2010, 15:47
von Leonidas
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.

Verfasst: Sonntag 3. Januar 2010, 15:49
von snafu
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.

Verfasst: Sonntag 3. Januar 2010, 15:52
von 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.

Verfasst: Sonntag 3. Januar 2010, 15:53
von hendrikS
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?

Verfasst: Sonntag 3. Januar 2010, 16:01
von Trundle
@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']

Verfasst: Sonntag 3. Januar 2010, 16:56
von Darii
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.

Verfasst: Sonntag 3. Januar 2010, 17:22
von hendrikS
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.

Verfasst: Sonntag 3. Januar 2010, 17:51
von snafu

Code: Alles auswählen

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

Verfasst: Sonntag 3. Januar 2010, 17:59
von lunar
Ja und? Der Hash eines Objekts ist nun mal nicht eindeutig. Aber was hat das jetzt mit der Diskussion zu tun?