Als Beispiel definieren wir eine Klasse Bruch
von Objekten, die
rationale Zahlen darstellen.
class Bruch:
def __init__(self, zaehler, nenner):
self.zaehler = zaehler
self.nenner = nenner
Das Schlüsselwort class
leitet die Klassendefinition ein und ist
gefolgt vom Namen der Klasse.
Innerhalb der Klasse definieren wir eine,
Konstruktor genannte, Methode __init__
, die bei der Erzeugung
von Objekten der Klasse ausgeführt wird. Die Methode hat hier drei
Parameter self,
zaehler
und nenner
Der erste Parameter muss bei der Erzeugung nicht angegeben
werden sondern verweist automatisch auf das neu erzeugte Objekt.
Die restlichen Parameter müssen bei der Erzeugung angegeben werden.
In unserem Beispiel speichern wir deren Werte in den Attributvariablen
zaehler
und nenner
auf die wir mit der Punkt-Schreibweise des Objektes in self
zugreifen können.
Attribute speichern den Zustand konstruierter Objekte
und sind überall innerhalb der Klassendefinition (und in Python sogar außerhalb dieser) sichtbar.
Um Objekte der Klasse Bruch zu erzeugen, rufen wir Bruch
als Funktion auf und
übergeben die vom Konstruktor erwarteten Argumente für Zähler und
Nenner. Auch diese Funktion nennen wir Konstruktor.
Sie erzeugt eine neue Instanz der zugehörigen Klasse und ruft dann die Methode __init__
auf.
Bisher haben wir Objekte meist ohne Verwendung expliziter Konstruktoren erzeugt. Für Zahlen, Wahrheitswerte, Zeichenketten und Listen bietet Python spezielle Syntax, die es erlaubt, Objekte kompakter zu initialisieren. Zum Beispiel bei Listen können wir allerdings auch explizite Konstruktoren verwenden, wie die folgenden Aufrufe zeigen.
>>> nums = [1,2,3]
>>> nums2 = list(nums)
>>> nums is nums2
False
>>> nums == nums2
True
Man kann von Listen also eine Kopie anlegen, indem man sie bei der Konstruktion eines neuen Objektes als Parameter an den list
Konstruktor übergibt.
Doch nun zurück zu der selbst definierten Klasse für Brüche.
>>> Bruch(3,4)
<__main__.Bruch object at 0x7fe84aed0970>
>>> Bruch(8,6)
<__main__.Bruch object at 0x7fe84ae1c310>
Durch Übergabe von Zähler und Nenner an den Konstruktor Bruch
wird jeweils ein neues Objekt erzeugt, das entsprechende Werte in den Attributen zaehler
und nenner
speichert. In Python werden die erzeugten Objekte standardmäßig durch Angabe des Klassennamens und der Speicheradresse angezeigt. Wir können eine alternative Darstellung definieren, indem wir eine Methode __str__
definieren, die von der str
-Funktion automatisch verwendet wird.
Dazu fügen wir innerhalb der Klassendefinition folgendes ein.
def __str__(self):
return str(self.zaehler) + "/" + str(self.nenner)
Auch diese Methode hat (wie alle Methoden) einen ersten Parameter (hier self
), der beim Aufruf automatisch auf das Objekt verweist, auf dem die Methode aufgerufen wird. Dadurch ist es möglich, in Methoden auf Attributvariablen (und andere Methoden) zuzugreifen.
Wir erzeugen erneut ein Bruch-Objekt und beobachten, wie es nun angezeigt wird.
>>> drei4tel = Bruch(3,4)
>>> drei4tel
<__main__.Bruch object at 0x7f2c8fcea970>
>>> str(drei4tel)
'3/4'
>>> print(drei4tel)
3/4
Wie wir sehen, wandelt auch print
das Argument automatisch mit Hilfe der __str__
-Methode in einen String um, wenn diese vorhanden ist. Indem wir zusätzlich eine Methode __repr__
definieren, die __str__
aufruft, können wir beeinflussen, wie Bruch-Objekte in der interaktiven Python-Umgebung angezeigt werden.
def __repr__(self):
return self.__str__()
Hier wird beim Aufruf von __str__
der erste Parameter automatisch mit dem Objekt in self
initialisiert, auf dem wir die Methode aufrufen.
Nun werden Brüche auch ohne expliziten Aufruf von str
oder print
in unserer eigenen Darstellung angezeigt.
>>> acht6tel = Bruch(8,6)
>>> acht6tel
8/6
Es wäre schön, wenn Brüche automatisch gekürzt würden. Dazu können wir Zähler und Nenner im Konstruktor durch deren größten gemeinsamen Teiler teilen. Zur Berechnung dessen verwenden wir den Algorithmus von Euklid.
Wir ersetzen also den Konstruktor __init__
wie hier gezeigt und fügen die Methode ggT
hinzu.
def __init__(self, zaehler, nenner):
gcd = self.ggT(zaehler, nenner)
self.zaehler = zaehler // gcd
self.nenner = nenner // gcd
def ggT(self, a, b):
while b != 0:
x = b
b = a % x
a = x
return a
Nun werden alle erzeugten Brüche intern gekürzt dargestellt also auch so angezeigt.
>>> Bruch(8,6)
4/3
Als nächstes wollen wir eine Methode zum Multiplizieren von Brüchen definieren. Diese Methode soll das Ergebnis als neues Objekt zurück liefern und die multiplizierten Objekte nicht verändern.
Zur Definition der Multiplikation definieren wir eine Methode mit dem Namen mal
. Deren Implementierung erzeugt ein neues Objekt der Klasse Bruch und greift sowohl auf die eigenen Attribute als auch auf diejenigen des übergebenen Argumentes zu.
def mal(self, other):
return Bruch(
self.zaehler * other.zaehler,
self.nenner * other.nenner
)
Nun können wir Brüche wie folgt multiplizieren.
>>> drei4tel = Bruch(3,4)
>>> acht6tel = Bruch(8,6)
>>> drei4tel.mal(acht6tel)
1/1
Wie wir sehen, wird das Ergebnis dabei automatisch gekürzt. Zur Implementierung des kürzenden Konstruktors haben wir eine Methode ggT
definiert, die wir auch außerhalb der Klassendefinition auf Bruch-Objekten aufrufen können.
>>> drei4tel.ggT(24,16)
8
Wir hatten nicht beabsichtigt, Brüchen die ggT
-Funktion als nach außen sichtbare Methode hinzuzufügen. Wir wollten diese lediglich im Konstruktor verwenden, um Brüche zu kürzen.
In Python können die Namen von Attributen und Methoden mit einem Unterstrich beginnen. Dadurch wird per Konvention signalisiert, dass diese Namen außerhalb der Klassendefinition nicht verwendet werden sollen.1 In anderen Sprachen kann die Sichtbarkeit von Attribute und Methoden gesteuert werden, um die Verwendung außerhalb der Klassendefinition zu verhindern.
Auch auf Attributvariablen können wir in Python von außen zugreifen, wie das folgende Beispiel zeigt.
>>> drei4tel.nenner = 3
>>> drei4tel
3/3
Um unsere Absicht zu kommunizieren, dass Brüche nicht mutierbar sein sollen, können wir die Attributvariablen mit einem Unterstrich am Anfang benennen. Der Zugriff von außen wird dadurch zwar nicht verhindert, aber solche Zugriffe sind zumindest am Unterstrich besser als (per Konvention) unzulässig zu erkennen. Lesender Zugriff von außen sollte jedoch weiterhin erlaubt sein. Dazu definieren wir Methoden, die die Werte von Zaehler und Nenner zurückliefern.
Hier ist noch einmal die komplette Definition der Bruch
-Klasse inklusive Verwendung von Unterstrichen um sogenannte private Bestandteile zu kennzeichnen.
class Bruch:
def __init__(self, zaehler, nenner):
gcd = self._ggT(zaehler, nenner)
self._zaehler = zaehler // gcd
self._nenner = nenner // gcd
def _ggT(self, a, b):
while b != 0:
x = b
b = a % x
a = x
return a
def zaehler(self):
return self._zaehler
def nenner(self):
return self._nenner
def mal(self, other):
return Bruch(
self._zaehler * other._zaehler,
self._nenner * other._nenner
)
def __str__(self):
return str(self._zaehler) + "/" + str(self._nenner)
def __repr__(self):
return self.__str__()
Bei Verwendung eines doppelten Unterstriches wird in Python eine Umbenennung vorgenommen, die eine Verwendung von außen zwar nicht verhindert, aber versehentliche Verwendung unwahrscheinlicher macht. ↩︎