Aufwärmen · Aufgabe 1 von 7
// A1.1 · ca. 30 Min. · Vertiefung
__repr__, __str__, __eq__ und __hash__
Nach dieser Lektion geben Sie einer Klasse ein hilfreiches repr und str, vergleichen ihre Objekte nach Wert und machen sie zu sicheren Set-Elementen und Dict-Schlüsseln.
Lektion 1 von 6 in A1 Das Datenmodell
Danach können Sie
- __repr__ und __str__ schreiben und vorhersagen, welche Methode print, f-Strings und Container nutzen
- __eq__ und __hash__ so schreiben, dass gleiche Objekte gleiche Hashes haben
- Erklären, warum eine Klasse mit nur __eq__ unhashbar ist und warum sich ein Hash nie ändern darf
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es vorher, bevor Sie weiterlesen: Die Klasse definiert __repr__ und __str__. Was gibt das aus?
class Point: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return f"Point({self.x}, {self.y})" def __str__(self): return f"({self.x}, {self.y})" p = Point(1, 2) print(p, [p])Üben · Aufgabe 3 von 7
Setzen Sie die eingebaute Funktion ein, damit der Hash aus denselben Feldern entsteht, die __eq__ vergleicht.
class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point): return NotImplemented return (self.x, self.y) == (other.x, other.y) def __hash__(self): return ____((self.x, self.y))return ((self.x, self.y))Üben · Aufgabe 4 von 7
Money definiert __eq__, aber kein __hash__. Was gibt das aus?
class Money: def __init__(self, cents): self.cents = cents def __eq__(self, other): return isinstance(other, Money) and self.cents == other.cents print(Money(5) == Money(5), Money.__hash__)Üben · Aufgabe 5 von 7
Die Klasse definiert __repr__ und __str__. Ordnen Sie jedem Ausdruck die Methode zu, die seinen Text liefert.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Das Ticket kommt in ein Set, danach ändert sich seine Nummer. Was gibt das aus?
class Ticket: def __init__(self, number): self.number = number def __eq__(self, other): return isinstance(other, Ticket) and self.number == other.number def __hash__(self): return hash(self.number) t = Ticket(1) open_tickets = {t} t.number = 2 print(t in open_tickets, Ticket(1) in open_tickets)Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie eine Klasse Money(cents, currency). repr liefert Money(250, 'EUR'), str liefert 2.50 EUR, zwei Objekte sind gleich, wenn cents und currency übereinstimmen, und ein Set aus Money(250, "EUR"), Money(250, "EUR") und Money(250, "USD") hat zwei Elemente.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Artikelnummern als Dict-Schlüssel
Ein Laden scannt Artikel als Produktcode plus Größe. Codes kommen in beliebiger Schreibweise, also speichert Sku sie in Großbuchstaben und vergleicht nach Wert. Weil __eq__ und __hash__ dieselben zwei Felder nutzen, fällt der doppelte Scan im Set zusammen, und eine neu gebaute Sku findet ihren Bestand im Dict. In der Liste erscheint repr, in den Berichtszeilen str.
main.py
class Sku:
"""A stock-keeping unit: product code plus size, compared by value."""
def __init__(self, code: str, size: str) -> None:
self.code = code.upper()
self.size = size
def __repr__(self) -> str:
return f"Sku({self.code!r}, {self.size!r})"
def __str__(self) -> str:
return f"{self.code}-{self.size}"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Sku):
return NotImplemented
return (self.code, self.size) == (other.code, other.size)
def __hash__(self) -> int:
return hash((self.code, self.size))
scanned = [Sku("tee", "M"), Sku("TEE", "M"), Sku("cap", "L")]
print("Scanned:", scanned)
print("Unique items:", len(set(scanned)))
stock = {Sku("TEE", "M"): 12, Sku("CAP", "L"): 3}
for sku in scanned:
print(f"{sku}: {stock[sku]} in stock")
print(Sku("tee", "M") == "TEE-M")
Ausführen mit
python main.pyAusgabe
Scanned: [Sku('TEE', 'M'), Sku('TEE', 'M'), Sku('CAP', 'L')]
Unique items: 2
TEE-M: 12 in stock
TEE-M: 12 in stock
CAP-L: 3 in stock
False- Die Liste zeigt jede Sku mit __repr__; die f-String-Zeilen verwenden __str__.
- Sku("tee", "M") und Sku("TEE", "M") sind gleich und haben denselben Hash, also behält das Set eine.
- stock[sku] klappt mit einem anderen Objekt als dem gespeicherten Schlüssel, weil Hash und == zusammenpassen.
- Beim Vergleich mit einem String liefert __eq__ NotImplemented, und Python fällt auf die Identität zurück: False.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übungen
Übung 1 von 2
Zwei Gesichter einer Farbe
Geben Sie der Klasse Color in main.py ein __repr__ und ein __str__. repr(Color(255, 128, 0)) muss Color(255, 128, 0) sein und str() der Hex-Code #ff8000, mit zwei kleingeschriebenen Hex-Ziffern je Kanal.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Beide Methoden bekommen nur self und müssen einen str zurückgeben, nicht ausgeben.
Hinweis 2
Die Formatangabe 02x schreibt ein int als zwei kleine Hex-Ziffern: f"{10:02x}" ergibt 0a.
Hinweis 3
return f"#{self.red:02x}{self.green:02x}{self.blue:02x}" für __str__ und f"Color({self.red}, {self.green}, {self.blue})" für __repr__.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
class Color:
"""An RGB colour with channels from 0 to 255."""
def __init__(self, red: int, green: int, blue: int) -> None:
self.red = red
self.green = green
self.blue = blue
def __repr__(self) -> str:
return f"Color({self.red}, {self.green}, {self.blue})"
def __str__(self) -> str:
return f"#{self.red:02x}{self.green:02x}{self.blue:02x}"
if __name__ == "__main__":
orange = Color(255, 128, 0)
print(repr(orange), orange)
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
class Color:
"""An RGB colour with channels from 0 to 255."""
def __init__(self, red: int, green: int, blue: int) -> None:
self.red = red
self.green = green
self.blue = blue
# Add __repr__: Color(255, 128, 0)
# Add __str__: #ff8000
if __name__ == "__main__":
orange = Color(255, 128, 0)
print(repr(orange), orange)
test_main.py
from main import Color
def test_repr():
"""repr() sieht aus wie der Aufruf, der die Farbe baut"""
got = repr(Color(255, 128, 0))
assert got == "Color(255, 128, 0)", f"repr(Color(255, 128, 0)) lieferte {got!r}, erwartet: 'Color(255, 128, 0)'"
def test_str():
"""str() ist der Hex-Code mit zwei Ziffern je Kanal"""
got = str(Color(255, 128, 0)), str(Color(0, 10, 255))
assert got == ("#ff8000", "#000aff"), f"str() lieferte {got!r}, erwartet: ('#ff8000', '#000aff')"
def test_print_uses_str():
"""Ein f-String verwendet __str__"""
got = f"colour: {Color(0, 0, 0)}"
assert got == "colour: #000000", f"der f-String ergab {got!r}, erwartet: 'colour: #000000'"
def test_list_uses_repr():
"""Eine Liste zeigt ihre Elemente mit __repr__"""
got = str([Color(1, 2, 3)])
assert got == "[Color(1, 2, 3)]", f"str([Color(1, 2, 3)]) lieferte {got!r}, erwartet: '[Color(1, 2, 3)]'"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 2
Punkte in einem Set
Point in main.py hat ein __repr__. Ergänzen Sie __eq__ und __hash__, sodass Punkte mit gleichem x und y gleich sind, ein Set nur einen davon behält und ein gleicher Punkt einen Dict-Eintrag findet. Für alles, was kein Point ist, muss __eq__ NotImplemented liefern.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Ohne __eq__ sind zwei Point(1, 2) verschieden, weil == auf die Identität zurückfällt.
Hinweis 2
Prüfen Sie in __eq__ zuerst isinstance(other, Point) und liefern Sie sonst NotImplemented.
Hinweis 3
Vergleichen Sie (self.x, self.y) == (other.x, other.y) und geben Sie in __hash__ hash((self.x, self.y)) zurück.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Point({self.x}, {self.y})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self) -> int:
return hash((self.x, self.y))
if __name__ == "__main__":
print({Point(1, 2), Point(1, 2)})
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Point({self.x}, {self.y})"
# Add __eq__ (NotImplemented for other types) and a matching __hash__.
if __name__ == "__main__":
print({Point(1, 2), Point(1, 2)})
test_main.py
from main import Point
def test_equal_points():
"""Zwei Punkte mit denselben Koordinaten sind gleich"""
assert Point(1, 2) == Point(1, 2), "Point(1, 2) == Point(1, 2) ist False: Vergleichen Sie x und y in __eq__"
assert Point(1, 2) != Point(2, 1), "Point(1, 2) != Point(2, 1) ist False"
def test_set_removes_duplicates():
"""Ein Set behält einen von zwei gleichen Punkten"""
got = len({Point(1, 2), Point(1, 2), Point(3, 4)})
assert got == 2, f"das Set enthält {got} Punkte, erwartet: 2"
def test_dict_key():
"""Ein gleicher Punkt findet den Dict-Eintrag"""
names = {Point(0, 0): "origin"}
got = names.get(Point(0, 0))
assert got == "origin", f"names.get(Point(0, 0)) lieferte {got!r}, erwartet: 'origin'"
def test_other_types():
"""__eq__ liefert NotImplemented für alles, was kein Point ist"""
got = Point(1, 2).__eq__((1, 2))
assert got is NotImplemented, f"Point(1, 2).__eq__((1, 2)) lieferte {got!r}, erwartet: NotImplemented"
assert Point(1, 2) != (1, 2), "Point(1, 2) darf nicht gleich dem Tupel (1, 2) sein"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHäufige Fehler
__eq__ definieren und __hash__ vergessen
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)
seen = {Point(1, 2)}
Was Python ausgibt
TypeError: cannot use 'Point' as a set element (unhashable type: 'Point')Warum, und die Lösung
Wer __eq__ überschreibt, setzt __hash__ auf None, und die Objekte können weder in ein Set noch Dict-Schlüssel sein. Ergänzen Sie def __hash__(self): return hash((self.x, self.y)) mit denselben Feldern, die __eq__ vergleicht. Sind die Objekte veränderlich, lassen Sie sie bewusst unhashbar.
__repr__, das keinen str zurückgibt
class Order:
def __init__(self, number):
self.number = number
def __repr__(self):
return self.number
print(repr(Order(42)))
Was Python ausgibt
TypeError: __repr__ returned non-string (type int)Warum, und die Lösung
__repr__ und __str__ müssen einen str liefern; Python prüft den Typ und löst sonst einen Fehler aus. Bauen Sie den Text: return f"Order({self.number!r})". Dasselbe gilt für __hash__, das ein int liefern muss.
__eq__, das denselben Typ beim anderen Objekt voraussetzt
class Point:
def __init__(self, x):
self.x = x
def __eq__(self, other):
return self.x == other.x
print(Point(1) == "a")
Was Python ausgibt
AttributeError: 'str' object has no attribute 'x'Warum, und die Lösung
Auf der anderen Seite von == kann jedes Objekt landen, etwa wenn eine Liste mit in durchsucht wird. Prüfen Sie zuerst den Typ und liefern Sie für fremde Objekte NotImplemented: if not isinstance(other, Point): return NotImplemented. Python fragt dann den anderen Operanden und antwortet zuletzt False.
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.