Magische Methoden und Operatorüberladung
Einführung

>>> a = 3 + 4 >>> a 7 >>> 3 + 5.543 8.543 >>> s = "Hello" >>> print(s + " World") Hello World >>>

Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'K' and 'K'
Übersicht über die magischen Methoden
Binäre Operatoren
Operator | Methode |
---|---|
+ | object.__add__(self, other) |
- | object.__sub__(self, other) |
* | object.__mul__(self, other) |
// | object.__floordiv__(self, other) |
/ | object.__truediv__(self, other) |
% | object.__mod__(self, other) |
** | object.__pow__(self, other[, modulo]) |
<< | object.__lshift__(self, other) |
>> | object.__rshift__(self, other) |
& | object.__and__(self, other) |
^ | object.__xor__(self, other) |
| | object.__or__(self, other) |
Erweiterte Zuweisungen
Operator | Methode |
---|---|
+= | object.__iadd__(self, other) |
-= | object.__isub__(self, other) |
*= | object.__imul__(self, other) |
/= | object.__idiv__(self, other) |
//= | object.__ifloordiv__(self, other) |
%= | object.__imod__(self, other) |
**= | object.__ipow__(self, other[, modulo]) |
<<= | object.__ilshift__(self, other) |
>>= | object.__irshift__(self, other) |
&= | object.__iand__(self, other) |
^= | object.__ixor__(self, other) |
|= | object.__ior__(self, other) |
Unäre Operatoren
Operator | Methode |
---|---|
- | object.__neg__(self) |
+ | object.__pos__(self) |
abs() | object.__abs__(self) |
~ | object.__invert__(self) |
complex() | object.__complex__(self) |
int() | object.__int__(self) |
long() | object.__long__(self) |
float() | object.__float__(self) |
oct() | object.__oct__(self) |
hex() | object.__hex__(self |
Vergleichsoperatoren
Operator | Methode |
---|---|
< | object.__lt__(self, other) |
<= | object.__le__(self, other) |
== | object.__eq__(self, other) |
!= | object.__ne__(self, other) |
>= | object.__ge__(self, other) |
> | object.__gt__(self, other) |
Beispielklasse: Length
In der folgenden Beispielklasse ,,Length'' wollen wir exemplarisch zeigen, wie man für eine eigene Klasse eine Addition mittels des ,,+''-Operators durch Überladung der __init__-Methode definieren kann. In der Klassendefinition befinden sich auch die magischen Methoden __str__ und __repr,__ die wir bereits zu Anfang des Kapitels besprochen hatten. Instanzen der Klasse Length sind Entfernungen. Eine Instanz beinhaltet die Länge der Strecke self.value und die Maßeinheit für diese Länge self.unit.Die Klasse ermöglicht einem das einfache Ausrechnen von Ausdrücken der folgenden Art:
2.56 m + 3 yd + 7.8 in + 7.03 cm
Unter Benutzung unserer zu schreibenden Klasse sieht das dann so aus:
>>> from unit_conversions import Length >>> L = Length >>> print(L(2.56,"m") + L(3,"yd") + L(7.8,"in") + L(7.03,"cm")) 5.57162 >>>
Die Klasse sieht wie folgt aus:
class Length: __metric = {"mm" : 0.001, "cm" : 0.01, "m" : 1, "km" : 1000, "in" : 0.0254, "ft" : 0.3048, "yd" : 0.9144, "mi" : 1609.344 } def __init__(self, value, unit = "m" ): self.value = value self.unit = unit def Converse2Metres(self): return self.value * Length.__metric[self.unit] def __add__(self, other): l = self.Converse2Metres() + other.Converse2Metres() return Length(l / Length.__metric[self.unit], self.unit ) def __str__(self): return str(self.Converse2Metres()) def __repr__(self): return str((self.value, self.unit)) if __name__ == "__main__": x = Length(4) print(x) print(repr(x)) y = Length(4.5, "yd") + x print(repr(y)) print(y)
Starten wir das Programm erhalten wir folgende Ausgaben:
4 (4, 'm') (8.87445319335083, 'yd') 8.114799999999999
Wir benötigen die Methode __iadd__, um die erweiterte Zuweisung zu implementieren:
def __iadd__(self, other): l = self.Converse2Metres() + other.Converse2Metres() self.value = l / Length.__metric[self.unit] return self
Damit sind wir dann in der Lage, Zuweisungen der folgenden Art durchzuführen:
x += Length(1) x += Length(4, "yd")
In der ersten erweiterten Zuweisung wird 1 Meter zu Length-Objekt x hinzugezählt, ohne dass wir die Einheit angeben müssen. Eigentlich wäre es doch schöner, wenn wir direkt eine Integer- oder eine Floatzahl zu einem Length-Objekt hinzuzählen könnten und unsere Implementierung würde diese Zahl gewissermaßen wie ein Length-Objekt behandeln. Dies lässt sich ganz einfach realisieren. Dazu ändern wir lediglich unser __add__-Methode so ab, dass sie den Typ von ,,other'' überprüft:
def __add__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() + other else: l = self.Converse2Metres() + other.Converse2Metres() return Length(l / Length.__metric[self.unit], self.unit ) def __iadd__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() + other else: l = self.Converse2Metres() + other.Converse2Metres() self.value = l / Length.__metric[self.unit] return self
Wenn man mit dieser Klasse eine Weile arbeitet, stellt sich mit Sicherheit eine neue Begehrlichkeit ein. Versucht man eine Integer- oder eine Floatzahl auf der linken Seite und ein Length-Objekt auf der rechten Seite bei der Addition zu verwenden erhält man eine Fehlermeldung:
>>> from unit_conversions import Length >>> x = Length(3, "yd") + 5 >>> x = 5 + Length(3, "yd") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'Length' >>>
Auch für diese Problemstellung gibt es natürlich eine Lösung in Python und die liefert die Methode __radd__. Führt der Aufruf von int.__add__(5,Length(3, "yd")) zu einer Ausnahme, sucht Python nach der Methode __radd__, da unser zweiter Parameter (Length(3, "yd")) eine Instanz der Klasse Length ist. Existiert diese Methode, wird sie mit Length.__radd__(Length(3, "yd"), 5) aufgerufen, was dem Aufruf 5 + Length(3, "yd") entspricht.
Damit sieht eine Implementierung von __radd__ exakt gleich aus wie __add__:
def __radd__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() + other else: l = self.Converse2Metres() + other.Converse2Metres() return Length(l / Length.__metric[self.unit], self.unit )
Es empfiehlt sich deshalb die Implementierung von __radd__ auf einen Aufruf von __add__ zu reduzieren:
def __radd__(self, other): return Length.__add__(self,other)
Im folgenden Diagramm veranschaulichen wir den Zusammenhang zwischen __add__ und __radd__:

Standardklassen als Basisklassen
Statt selbstdefinierter Klassen kann man auch Standardklassen wie beispielsweise int, float, dict oder list als Basisklasse verwenden, um neue Klassen abzuleiten. Man kann beispielsweise zusätzliche Methoden für Standardklassen definieren.Die Verarbeitung von Stapelspeichern (auch Kellerspeicher genannt, im Englischen stack) wird in Python, wie wir gesehen haben, mittels append und pop realisiert. Üblicherweise, d.h. in anderen Programmiersprachen, werden meistens die Funktionen push (deutsch: einkellern) und pop (deutsch: auskellern) verwendet.
Wir wollen im folgenden Beispiel die Klasse ,,list'' um eine Methode ,,push'' erweitern, die genau das Gleiche macht wie append:
class Plist(list): def __init__(self, l): list.__init__(self, l) def push(self, item): self.append(item) if __name__ == "__main__": x = Plist([3,4]) x.push(47) print(x)