Überladen von Operatoren
Alles in Python ist ein Objekt, also zum Beispiel Zahlen, Strings, Funktionen, oder Instanzen eigenes entwickelter Klassen. In Python gibt es die Klasse object, welche implizit die Elternklasse aller Klassen bzw. Datentypen ist.
Beispielsweise kann mit der built-in Funktion isinstance geprüft werden, ob ein Objekt eine Instanz von einer bestimmten Klasse ist. isinstance prüft auch entlang der Klassenhierarchie, also prüft auch, ob ein Objekt eine Instanz von einer bestimmten Basisklasse ist.
print(isinstance(3, object)) # True
print(isinstance(3, int)) # True
print(isinstance("3", object)) # True
print(isinstance("3", str)) # True
print(isinstance("3", int)) # False
Alle Operatoren (+, -, *, etc.) bzw. auch built-in Funktionen welche in Python definiert sind, sind generell für Objekte bzw. Variablen vom Typ object vorgesehen. Also generell jegliches Objekt kann über die Standardoperatoren mit anderen Objekten verknüpft werden.
Im folgenden soll examplarisch für die eigene Klasse Point demonstriert werden, wie die Implementierung von unterschiedlichen Magic Methoden das Überladen von Operatoren realisiert wird.
Die Klasse Point repräsentiert einen Punkt über eine x bzw. eine y Koordinate. Die beiden Koordinaten werden dabei als private Objekteigenschaften geführt.
class Point:
def __init__(self, x=0, y=0):
self.__x = x
self.__y = y
Beispiel: Überladen der String Konvertierung
Die bekannte built-in Funktion print wandelt die übergebenen Parameter in Strings um str(param)und gibt das Ergebnis aus, durch die Implementierung der Magic Methode __str__ kann bestimmt werden, wie ein beliebiges Objekt in einen String umgewandelt wird.
Am Beispiel der Klasse Point würde die Übergabe einer Instanz an die Funktion print folgende Ausgabe liefern:
p = Point(3, -1)
print(p) # <__main__.Point object at 0x0000022CE2327820>
Gewünscht wäre es jedoch, dass dies etwas sinnvoller, wie zum Beispiel (+3.00, -1.00) als Ausgabe hätte. Mit der Implementierung der Methode __str__ kann dies bewerkstelligt werden:
class Point:
def __init__(self, x=0, y=0):
self.__x = x
self.__y = y
def __str__(self):
return "({:+.2f}, {:+.2f})".format(self.__x, self.__y)
p = Point(3, -1)
print(p) # (+3.00, -1.00)
Konvertierung von Datentypen
Python bietet die Funktionen str, int, float oder complex, um die Konvertierung in einen dieser Datentypen durchzuführen. Es kann beispielsweise die Zahl 3 in einen String überführt werden:
example = str(3)
print(type(example)) # <class 'str'>
Eine eigene Klasse kann durch die Implementierung der Magic Methoden __str__, __int__, __float__ oder __complex__ die Konvertierung in einen dieser Datentypen realisieren. Im Beispiel oben wurde durch die Implementierung der Methode __str__ gezeigt wie die Klasse Point in einen String konvertiert wird.
Beispiel: Überladen der + Operation
Die Operation + wird zwischen Zahlen als klassische Addition implementiert, bei Strings kommt es zu einer sog. Konkatenierung. Durch die Implementierung der Magic Methode __add__ kann die + Operation beliebig realisiert werden.
Am Beispiel der Klasse Point sollen zwei Instanzen mit der + verknpüft werden. Die Klasse Point hat die Methode __add__ jedoch nicht implementiert. Folgender Quellcode führt deshalb zu einem TypeError:
p1 = Point(3, -1)
p2 = Point(1, 2)
print(p1 + p2)
Ausgabe der Fehlermeldung:
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'
Mit der Implementierung der Methode __add__ wird das gewünschte Ergebnis realisiert:
class Point:
def __init__(self, x=0, y=0):
self.__x = x
self.__y = y
def __str__(self):
return "({:+.2f}, {:+.2f})".format(self.__x, self.__y)
def __add__(self, other):
if type(other) == Point:
return Point(self.__x + other.__x, self.__y + other.__y)
else:
return Point(0, 0)
p1 = Point(3, -1)
p2 = Point(1, 2)
print(p1 + p2) # (+4.00, +1.00)
Überladen von Operatoren
Neben der + Operation können generell alle weiteren Operationen überladen werden. Beispielsweise sind folgende Magic Methoden definiert:
+Operation mit__add__-Operation mit__sub__*Operation mit__mul__/Operation mit__truediv__
Operationen werden immer von links nach rechts ausgeführt. Um speziell die rechte Seite einer Operation zu implementieren kann eine Methode mit r-Prefix verwendet werden, zB __radd__.
In Python gibt es die spezielle Syntax um eine Zuweisung mit einer Operation zu verknüpfen (zB += oder -=). Um diese Operationen zu Überladen kann mit einem i-Prefix gearbeitet werden, zB __iadd__.
Beispiel: Überladen des == Vergleichs
Der Vergleich == prüft ob 2 Objekte gleich sind. Für eigene Klassen kann durch die Implementierung der Methode __eq__ bestimmt werden, ob 2 Instanzen gleich sind.
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # False
Die Defaultimplementierung des Vergleichs prüft, ob die beiden Objekte p1 und p2 identisch sind. Dies wird durch die Identitätsoperation is durchgeführt. Im Fall von p1 und p2 ist dies nicht der Fall, somit resultiert der Vergleich in False.
Identitätsoperation
Die Identitätsoperation prüft nicht ob zwei Variablen den selben Wert haben, sondern ob sie dasselbe Objekt im Hauptspeicher referenzieren. Mit der build-in Funktion id wird ein eindeutiger Zahlenwert für jede Variable bestimmt, welcher auf der Speicheradresse im Hauptspeicher basiert.
a = "ab"
b = "ab"
c = "".join(["a", "b"])
print(a is b) # True
print(a is c) # False
print(a == b) # True
print(a == c) # True
Variablen können das selbe Objekt referenzieren. Im Beispiel referenzieren a und b den String "ab", welcher einmalig im Speicher liegt, jedoch über die 2 Variablen a und b referenziert wird. Deshalb resultiert der Vergleich a is b mit True.
Die join Methode erzeugt ein neues Objekt im Speicher, welches den String "ab" repräsentiert. a und c verweisen auf 2 unterschiedliche Objekte im Speicher, deshalb resultiert der Vergleich a is c in False, obwohl der Vergleich der Werte a == c in True resultiert.
Zusammenfassend ist die Vergleichsoperation a is b ein Shortcut für den Vergleich id(a) == id(b).
Mit der Implementeriung der __eq__ Methode wird der Vergleich von Instanzen der Klasse Point definiert. Der Vergleich prüft die Werte, also die Gleichheit von den x und y Koordinaten. Der Vergleich von p1 und p2 resultiert somit in True.
class Point:
def __init__(self, x=0, y=0):
self.__x = x
self.__y = y
def __str__(self):
return "({:+.2f}, {:+.2f})".format(self.__x, self.__y)
def __add__(self, other):
if type(other) == Point:
return Point(self.__x + other.__x, self.__y + other.__y)
else:
return Point(0, 0)
def __eq__(self, other):
if type(other) == Point:
return self.__x == other.__x and self.__y == other.__y
return False
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True