Zum Inhalt

Ü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