Eigene Objekte wie eingebaute Typen verhalten lassen
Gibt man ein gewöhnliches Objekt aus, zeigt Python etwa <__main__.Position object at 0x7f3a>, und Position("A", 1) == Position("A", 1) ist False, weil zwei Objekte standardmäßig nur gleich sind, wenn es dasselbe Objekt ist. Spezielle Methoden (auch Dunder-Methoden genannt, wegen der doppelten Unterstriche) lassen Ihre Klasse in Pythons Syntax einhaken: __repr__ steuert die Darstellung eines Objekts, __eq__ steuert ==, __add__ steuert +, __lt__ steuert < und so weiter. Sie rufen sie nie direkt auf; Python tut es, wenn Sie repr(x), a == b, a + b oder sorted(items) verwenden.
class Plain:
def __init__(self, cents):
self.cents = cents
a, b = Plain(500), Plain(500)
print(a == b) # False: different objects
print(a.cents == b.cents) # True
print("object at 0x" in str(a)) # True: the default display is unhelpful__repr__: das Objekt klar darstellen
__repr__ gibt einen String zurück, der das Objekt für Entwickler beschreibt. Üblich ist, dass er wie der Code aussieht, der es erzeugen würde: Money(1999, 'USD'). Listen, Debugger und Fehlermeldungen verwenden ihn, was Fehler viel lesbarer macht. print(x) und str(x) nutzen ebenfalls __repr__, sofern Sie nicht zusätzlich __str__ für einen schöneren Text für Nutzer definieren. Er muss den String zurückgeben, nicht ausgeben, und {self.currency!r} in einem f-String liefert die Form mit Anführungszeichen für ein String-Attribut.
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __repr__(self):
return f"Money({self.cents}, {self.currency!r})"
price = Money(1999)
print(price) # Money(1999, 'USD')
print([Money(5), Money(7, "EUR")]) # [Money(5, 'USD'), Money(7, 'EUR')]__eq__ und __hash__: was heißt gleich?
__eq__(self, other) entscheidet self == other. Vergleichen Sie die Felder, die die Identität ausmachen, und geben Sie, wenn other nicht der erwartete Typ ist, den Sonderwert NotImplemented zurück (nicht False): Python probiert dann die andere Seite und antwortet zuletzt selbst False, sodass Money(1) == 5 unbedenklich ist. Wer __eq__ definiert, schaltet das Hashing ab: Objekte sind dann weder Mengenmitglieder noch Dictionary-Schlüssel, außer Sie definieren __hash__ passend dazu, etwa hash((self.cents, self.currency)).
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __eq__(self, other):
if not isinstance(other, Money):
return NotImplemented # let Python decide
return self.cents == other.cents and self.currency == other.currency
def __hash__(self):
return hash((self.cents, self.currency))
print(Money(500) == Money(500)) # True
print(Money(500) == Money(500, "EUR")) # False
print(Money(500) == 500) # False, no error
print(len({Money(1), Money(1)})) # 1: usable in sets nowOperatoren und Ordnung
__add__(self, other) gibt Ihrer Klasse +. Liefern Sie ein neues Objekt zurück, statt self zu ändern, und lehnen Sie Unsinn ab: Dollar zu Euro zu addieren muss ValueError auslösen, eine bloße Zahl zu addieren soll NotImplemented zurückgeben. __lt__(self, other) gibt <, und das allein genügt sorted(), min() und max(), um Ihre Objekte zu ordnen, denn sie müssen nur fragen „ist a kleiner als b?“. Ergänzen Sie auch __le__, __gt__, __ge__ oder lassen Sie functools.total_ordering sie ableiten.
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __repr__(self):
return f"Money({self.cents}, {self.currency!r})"
def __add__(self, other):
if not isinstance(other, Money):
return NotImplemented
if other.currency != self.currency:
raise ValueError("cannot add different currencies")
return Money(self.cents + other.cents, self.currency)
def __lt__(self, other):
if not isinstance(other, Money):
return NotImplemented
return self.cents < other.cents
total = Money(150) + Money(275)
print(total) # Money(425, 'USD')
print(sorted([Money(30), Money(10), Money(20)])) # [Money(10, 'USD'), Money(20, 'USD'), Money(30, 'USD')]
print(sum([Money(1), Money(2)], Money(0))) # Money(3, 'USD')__repr__ sollte alles zeigen, was __eq__ betrachtet. Geben Sie + keine überraschenden Nebenwirkungen: Es soll einen neuen Wert zurückgeben und beide Operanden unberührt lassen, wie Zahlen es tun. Die Lab-Tests prüfen diese Punkte: Originale bleiben unverändert, unterschiedliche Währungen werden abgelehnt, fremde Typen ergeben NotImplemented.__repr__gibt einen String zurück (wieMoney(1999, 'USD')); sie gibt nichts aus.__eq__vergleicht Felder und gibt für fremde TypenNotImplementedzurück; ergänzen Sie__hash__, um Hashing zu behalten.__add__gibt ein neues Objekt zurück;__lt__genügt fürsorted,minundmax.
Als Nächstes: Dataclasses, die __init__, __repr__ und __eq__ für Sie schreiben.
Lab: Was bewertet wird
- Mit
__repr__ein Money-Objekt klar darstellen25 Pkt. - Mit
__eq__festlegen, wann zwei Beträge gleich sind25 Pkt. - Mit
__add__zwei Beträge addieren25 Pkt. - Mit
__lt__Beträge sortierbar machen25 Pkt.