Kurs
Einer der Gründe, warum Python so mächtig ist, sind die sogenannten Magic Methods. In Pythonista-Kreisen hörst du dafür oft auch den Begriff „Dunder Methods“ – abgeleitet von den doppelten Unterstrichen, mit denen sie beginnen und enden.
Ihre Aufgabe in Python ist das sogenannte „Operator Overloading“. Dahinter steckt schlicht, dass ein vordefinierter Operator eine erweiterte Bedeutung erhält. Anders gesagt: Mit Operator Overloading fügst du eigenen Klassen benutzerdefinierte Verhaltensweisen hinzu, damit sie mit Pythons Operatoren und integrierten Funktionen zusammenspielen.
In diesem Artikel tauchen wir tiefer in Pythons Magic Methods ein, indem wir:
- den Unterschied zwischen Methoden und Funktionen klären,
- erklären, was Magic Methods sind,
- und Python-Beispiele zeigen.
Öffne dieses DataLab-Workbook, um den Code aus dem Tutorial nachzuvollziehen.
Python-Methoden vs. Funktionen
Bevor wir Magic Methods besprechen, sollten wir die Unterschiede zwischen einer Methode und einer Funktion in Python verstehen.
Python nutzt Methoden, um das Verhalten eines Objekts abzubilden. Methoden sind also an ein Objekt gebunden und führen Operationen auf den darin enthaltenen Daten aus. Ein zentrales Unterscheidungsmerkmal ist der Parameter self, der in einer Instanzmethode immer als erstes Argument übergeben wird.
Hinweis: Der Name muss nicht zwingend self sein, aber Pythonistas haben sich darauf geeinigt, dass es Best Practice ist.
# Example of a method
class Human:
# This is a method
def walk(self):
print("I am walking")
Methoden hängen von der Klasse ab, zu der sie gehören. Um eine Methode aufzurufen, musst du daher zuerst eine Instanz der Klasse erzeugen und greifst dann mit dem Punktoperator darauf zu.
# This will work
joe = Human()
joe.walk()
"""
I am walking
"""
# This will throw a NameError
walk()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
/tmp/ipykernel_153/2327344440.py in <module>
4
5 # This will throw a NameError
----> 6 walk()
NameError: name 'walk' is not defined
Eine Funktion hingegen ist unabhängig: Du musst dafür keine Klasseninstanz erzeugen.
Python-Funktionen sind Anweisungen, die als Codeblock erst bei Aufruf ausgeführt werden. Sie sind unabhängig von einer Klasse definiert und können daher direkt über ihren Namen aufgerufen werden. Da sie nicht an eine Klasse gebunden sind, übergeben wir kein self – Parameter sind optional.
Im Kern ist eine Funktion einfach eine klar definierte Abfolge von Schritten, die der Interpreter ausführt, um eine Aufgabe zu erledigen.
# Example of a function
def run():
print("I am running.")
run()
"""
I am running.
"""
Wie wir im Artikel How to call and write functions zeigen, gibt es zwei Arten von Funktionen: eingebaute und benutzerdefinierte.
- Built-in-Funktionen: Funktionen, die zur Python-Standardbibliothek gehören.
- Benutzerdefinierte Funktionen: Von Nutzerinnen und Nutzern definierte Funktionen, die in Programmen aufgerufen werden können.
In vieler Hinsicht sind Methoden und Funktionen identisch – beide werden mit def definiert. Der grundlegende Unterschied: Methoden sind an ein Objekt gebunden und arbeiten auf den Daten einer Klasse, während Funktionen unabhängig definiert sind und überall im Programm per Name aufgerufen werden können.
Was sind Pythons Magic Methods?
Zu Beginn haben wir erwähnt, dass Magic Methods wegen ihrer doppelten Unterstriche auch Dunder Methods heißen. Gemeint ist: Alle Magic Methods in Python beginnen und enden mit zwei Unterstrichen.
Die bekannteste Magic Method ist __init__. Sie ermöglicht es, eine Klasse mit bestimmten Attributen zu initialisieren.
class Employee:
def __init__(self, name, age, role):
self.name = name
self.age = age
self.role = role
Um eine Instanz dieser Klasse zu erzeugen, müssen wir die Attribute name, age und role angeben. Zum Beispiel:
user_1 = Employee(name="John", age=24, role="Data Scientist")
Wenn wir das nicht tun, löst Python einen TypeError aus.
Die __init__-Methode gehört zur Kategorie „Initialisierung und Konstruktion“, ist aber nur eine von vielen. Die folgende Tabelle zeigt weitere Magic Methods aus dieser Kategorie.
|
__new__(cls, other) |
Erzeugt eine neue Instanz einer Klasse. |
|
__init__(self, other) |
Wird nach dem Erzeugen einer neuen Instanz aufgerufen; alle an den Konstruktor übergebenen Attribute werden gesetzt. |
|
__del__(self) |
Auch „Destructor“ genannt; wird aufgerufen, wenn eine Instanz zerstört werden soll. |
Am einfachsten stellst du dir Magic Methods als eine Art Vertrag zwischen deiner Implementierung und dem Python-Interpreter vor. Ein Vertragsbestandteil lautet, dass Python unter der Haube bestimmte Aktionen ausführt, wenn bestimmte Situationen eintreten – etwa das Initialisieren einer Klasseninstanz mit bestimmten Attributen.
Mit anderen Worten: Du rufst Magic Methods nicht direkt auf – Python übernimmt das automatisch im Hintergrund.
Methoden zur Zeichenkettenrepräsentation
Python ist eine objektorientierte Programmiersprache (OOP) – in Python ist alles ein Objekt. Wenn du ein neues Objekt erstellst, entsteht implizit ein verwandtes Objekt, weil alle Klassen von object erben.
Hinweis: Du kannst die Vererbung auch explizit machen, indem du object als Basis an deine Klasse übergibst.
# This works
class Car:
pass
# This also works
class Vehicle(object):
pass
car = Car()
vehicle = Vehicle()
print(car, vehicle, sep="\n")
"""
<__main__.Car object at 0x7f80449dc0a0>
<__main__.Vehicle object at 0x7f80449dc1f0>
"""
Im Code oben haben wir zwei leere Klassen erstellt – und dennoch wusste der Interpreter, was er beim Print-Aufruf ausgeben soll. Das ist möglich, weil alle Klassen von object (der Elternklasse aller Klassen in Python) erben. Diese besitzt die Magic Method __repr__, die vom print-Aufruf genutzt wird, um einen Wert an das Hauptprogramm zurückzugeben.
Wer mit Vererbung vertraut ist, weiß: Eine Kindklasse kann Methoden der Elternklasse überschreiben, indem sie eine eigene Methode mit demselben Namen definiert.
# Overriding the object __repr__
class Vehicle:
def __repr__(self):
return f"{self.__class__.__qualname__}"
vehicle = Vehicle()
print(vehicle)
"""
Vehicle
"""
Du kannst auch die Magic Method __str__ verwenden, um die Darstellung für print zu steuern.
Tatsächlich dient __repr__ als Fallback für __str__. Ruft man print auf, sucht Python zuerst nach __str__ in deiner Klasse und greift, falls nicht vorhanden, auf __repr__ zurück.
# Using __str__ for string representation
class Vehicle:
def __str__(self):
return f"{self.__class__.__qualname__}"
vehicle = Vehicle()
print(vehicle)
"""
Vehicle
"""
In der folgenden Tabelle findest du weitere Magic Methods zur Zeichenkettenrepräsentation.
|
__str__(self) |
Wird von Pythons integrierter Funktion str() und vom print-Aufruf genutzt, um die informelle String-Darstellung eines Objekts auszugeben. Sie muss kein gültiger Python-Ausdruck sein. |
|
__repr__(self) |
Wird von Pythons integrierter Funktion repr() und bei String-Konvertierungen genutzt, um die offizielle Darstellung eines Objekts auszugeben. Ideal ist ein gültiger Python-Ausdruck; wenn das nicht möglich ist, ist eine nützliche Beschreibung als String bevorzugt. |
|
__unicode__(self) |
Implementiert Pythons eingebaute unicode()-Funktion und gibt ein Unicode-Objekt zurück. |
|
__hash__(self) |
Wird von Pythons integrierter Funktion hash() und bei Operationen auf Elementen gehashter Collections genutzt. Die Methode sollte eine ganze Zahl zurückgeben. |
|
__nonzero__(self) |
Wird von Pythons integrierter Operation bool() für Wahrheitswerttests aufgerufen. Die Methode sollte True oder False (oder 1 bzw. 0) zurückgeben. |
„Magische“ Methoden für Kapselung
Kapselung ist ein Grundprinzip der objektorientierten Programmierung (OOP). Es bedeutet, Daten mit den Methoden zu bündeln, die auf ihnen arbeiten, oder den direkten Zugriff auf bestimmte Komponenten eines Objekts einzuschränken.
Entwicklerinnen und Entwickler aus anderen OOP-Sprachen kritisieren oft, dass Python Kapselung in Klassen nicht streng durchsetzt. Sie sind es gewohnt, explizite Zugriffsmodifizierer für Methoden und Felder zu haben, etwa private Attribute mit öffentlichen Gettern und Settern. In Python erfolgt viel Kapselung hingegen über Magic Methods.
Beispielsweise ist __setattr__ eine Lösung zur Kapselung, mit der wir ein Verhalten für das Zuweisen von Attributen definieren können – unabhängig davon, ob das Attribut zuvor existierte. Anders gesagt: Wir können Regeln für Änderungen an Attributwerten festlegen.
import string
class Alphabet():
def __setattr__(self, name, value):
self.__dict__[name] = value.upper()
a = Alphabet()
# Assinging an attribute that does not exist
# The setattr should convert it to caps despite being set as lowercase
a.alphabet = string.ascii_lowercase
print(a.alphabet)
Im Beispiel oben haben wir eine Klasse Alphabet ohne Attribute erstellt, Python aber mitgeteilt, dass jedes gesetzte Attribut der Instanz automatisch in Großbuchstaben umgewandelt werden soll.
OOP ist nicht jedermanns Alltag. Wenn du dich hier auffrischen willst, schau dir den Python Programmer Track von DataCamp an.
Mathematische Magic Methods
Eine weitere Kategorie sind die mathematischen Magic Methods (auch als arithmetische Operatoren bekannt). Ausdrücke werden in Python mithilfe spezieller Symbole – den Operatoren – gebildet. Damit solche Operatoren in Ausdrücken funktionieren, brauchen die beteiligten Objekte passende Magic Methods. Andernfalls wirft Python einen TypeError.
class Numbers:
def __init__(self, a, b):
self.a = a
self.b = b
set_a = Numbers(2, 4)
set_b = Numbers(3, 5)
print(set_a + set_b)
"""
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_152/3639634723.py in <module>
7 set_b = Numbers(3, 5)
8
----> 9 print(set_a + set_b)
TypeError: unsupported operand type(s) for +: 'Numbers' and 'Numbers'
"""
Hier haben wir mehrere Instanzen der Klasse Numbers erzeugt und versucht, sie zu addieren – erwartungsgemäß führt das zum Fehler. Eine Möglichkeit wäre, eine Methode in der Klasse zu schreiben, die Zahlen addiert. Damit könnten wir aber keine Ausdrücke mit Operatoren formulieren.
Besser ist es, die Magic Method __add__ zu implementieren:
# Using add operator
class Numbers:
def __init__(self, a, b):
self.a = a
self.b = b
def __add__(self, other):
# Only permit Numbers objects to be added
if not isinstance(other, Numbers):
return NotImplemented
return Numbers(other.a + self.a, other.b + self.b)
def __repr__(self):
return f"{self.__class__.__qualname__}({self.a}, {self.b})"
a = Numbers(2, 4)
b = Numbers(3, 5)
print(a + b) # Should result in Numbers(5, 9)
"""
RandomNumbers(5, 9)
"""
Das Beispiel oben erlaubt nur die Addition zweier Numbers-Objekte. Implementieren wir nun eine weitere Magic Method, damit wir ein Numbers-Objekt mit einer ganzen Zahl multiplizieren können.
# Multiplying Numbers with an integer
class Numbers:
def __init__(self, a, b):
self.a = a
self.b = b
def __add__(self, other):
# Only permit Numbers objects to be added
if not isinstance(other, Numbers):
return NotImplemented
return Numbers(other.a + self.a, other.b + self.b)
def __mul__(self, other):
if not isinstance(other, int):
return NotImplemented
return Numbers(self.a * other, self.b * other)
def __repr__(self):
return f"{self.__class__.__qualname__}({self.a}, {self.b})"
a = Numbers(2, 4)
print(a * 5) # Should return 10, 20
"""
RandomNumbers(10, 20)
"""
Durch __mul__ können wir nun die Zahlen unserer benutzerdefinierten Klasse mit einer ganzen Zahl multiplizieren.
Beachte: Das Numbers-Objekt steht links vom *-Operator – das ist kein Zufall. Setzen wir es auf die rechte Seite und schauen, was passiert.
- snip --
a = Numbers(2, 4)
print(3 * a)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_152/2857655049.py in <module>
1 a = Numbers(2, 4)
----> 2 print(3 * a)
TypeError: unsupported operand type(s) for *: 'int' and 'Numbers'
Python wirft einen TypeError.
Unsere __mul__-Implementierung deckt nur den Fall ab, dass das Numbers-Objekt links vom *-Operator steht. Um die Funktionalität zu erweitern, brauchen wir die Reverse-Magic-Method __rmul__.
# Enabling reverse multiplication
class Numbers:
def __init__(self, a, b):
self.a = a
self.b = b
def __add__(self, other):
# Only permit Numbers objects to be added
if not isinstance(other, Numbers):
return NotImplemented
return Numbers(other.a + self.a, other.b + self.b)
def __mul__(self, other):
if not isinstance(other, int):
return NotImplemented
return Numbers(self.a * other, self.b * other)
def __rmul__(self, other):
return self.__mul__(other)
def __repr__(self):
return f"{self.__class__.__qualname__}({self.a}, {self.b})"
a = Numbers(2, 4)
print(5 * a) # Should return 10, 20
"""
Numbers(10, 20)
"""
Jetzt können wir Multiplikationsausdrücke bilden, ohne darauf achten zu müssen, auf welcher Seite des Operators das Numbers-Objekt steht.
Fazit
Das Grundprinzip beim Einsatz von Pythons Magic Methods bleibt gleich: Du erstellst eine Klasse und überschreibst Magic Methods der Basisklasse object. Es gibt sehr viele Magic Methods in Python – ein vollständiger Überblick würde den Rahmen eines Tutorials sprengen. Sieh dir daher unbedingt die Python-Dokumentation an, um das gesamte Spektrum kennenzulernen und so deine Python-Programmierkenntnisse auszubauen.