OOP in Python kompakt: Klassen sind Baupläne für Objekte. Sie lernen Instanzen, Attribute und Methoden, die Rolle von self und __init__ sowie die Python-spezifische Sicht auf Kapselung und Vererbung kennen.
Python setzt bei „privaten“ Implementierungsdetails überwiegend auf Konventionen und Properties; doppelte Unterstriche bewirken Name Mangling, keinen unüberwindbaren Zugriffsschutz.
Klassen
objektorientierte Programmierung
Auch wenn Python ohne Wenn und Aber eine objektorientierte Programmiersprache ist, sind wir in den vorhergehenden Kapiteln nur indirekt auf die objektorientierte Programmierung (OOP) eingegangen. Mit Python lassen sich kleine Skripte oder Programme einfach und effizient schreiben, auch ohne dass man sie objektorientiert modelliert. Gerade totale Programmieranfänger finden es erfahrungsgemäß einfacher, wenn sie nicht sofort mit allen Prinzipien der OOP konfrontiert werden. Sie haben genügend Probleme, Zuweisungen, bedingte Anweisung oder Schleifen zu verstehen und vor allem richtig anzuwenden. Aber in vielen Situationen stellt die OOP eine deutliche qualitative Verbesserung der Implementierung eines Problems dar.
Aber auch wenn wir die objektorientierte Programmierung in den bisherigen Kapiteln vermieden haben, so war sie dennoch in unseren Übungen und Beispielen meistens präsent. Wir haben Objekte und Methoden von Klassen benutzt, ohne eigentlich von ihrer Existenz zu wissen. In diesem Kapitel geben
wir nun eine grundlegende Einführung in den objektorientierten Ansatz von Python. OOP
ist eine der mächtigsten Programmiermöglichkeiten von Python, aber, wie wir gesehen
haben, muss man sie dennoch nicht nutzen, d.h. man kann auch umfangreiche und effiziente
Programme ohne Verwendung von OOP-Techniken schreiben.
Auch wenn viele Programmierer und Informatiker die OOP für eine moderne Errungenschaft
halten, so gehen ihre Wurzeln bis in die 1960er-Jahre zurück. Die erste Programmiersprache,
die Objekte verwendete, war ,,Simula 67'' von Ole-Johan Dahl und Kristen
Nygard. Wie der Name sagt wurde diese Sprache bereits im Jahre 1967 eingeführt.
Objekte, Instanzen und Klassen
Ein Grundkonzept der objektorientierten Programmierung besteht darin, Daten und deren
Funktionen (Methoden), - d.h. Funktionen, die auf diese Daten angewendet werden können -,
in einem Objekt zusammenzufassen und nach außen zu kapseln, sodass die Benutzer der Klassen
und auch Methoden fremder Objekte diese Daten nicht manipulieren können.
Objekte werden über Klassen definiert. Klassen sind Vorlagen - man könnte auch ,,Baupläne'' sagen -, nach denen Objekte, - die man in diesem Zusammenhang auch als Instanzen bezeichnet - zur Laufzeit des Programmes erzeugt werden. Eine Klasse stellt eine formale Beschreibung dar, wie ein Objekt beschaffen ist, d.h. welche Attribute und welche Methoden sie hat.
Eine Klasse darf nicht mit einem Objekt verwechselt werden. Statt von einem Objekt
spricht man auch von einer Instanz einer Klasse, d.h. die Begriffe ,,Instanz''
und ,,Objekt'' werden meistens synonym benutzt.

Man kann eine Klasse auch im übertragenen Sinne wie ein Koch- oder Backrezept sehen. Betrachten wir beispielsweise das Rezept eines Erdbeerkuchens. Ein solches Rezept kann man prinzipiell als eine Klasse ansehen. Das heißt, das Rezept bestimmt, wie eine Instanz der Klasse beschaffen sein muss. Backt jemand einen Kuchen nach diesem Rezept, dann schafft er eine Instanz oder ein Objekt dieser Klasse. Es gibt dann verschiedene Methoden, diesen Kuchen zu verarbeiten oder zu verändern, wie zum Beispiel ,,Teig anrühren''. Ein Erdbeerkuchen gehört in eine übergeordnete Klasse ,,Kuchen'', die ihre Eigenschaften, z.B. dass ein Kuchen sich als Nachtisch nutzen lässt, an Unterklassen wie Erdbeerkuchen, Rührkuchen, Torten und so weiter vererbt.
Ein Objekt bezeichnet in der OOP die Abbildung eines realen Gegenstandes mit seinen Eigenschaften und Verhaltensweisen (Methoden) in ein Programm.
Ein Objekt kann immer durch zwei Dinge beschrieben werden:
- was es tun kann oder was wir in einem Programm mit ihm tun können,
- was wir über es wissen.

Im nebenstehenden Diagramm sehen wir die Modellierung einer Klasse Konto mit ihren Eigenschaften und Methoden. Die Eigenschaften der Klasse Konto müssen auf jeden Fall eine "Kontonummer" und einen "Kontostand" haben. Dazu braucht man auch Eigenschaften wie "Verfügungsberechtigte" und "Kreditrahmen". Natürlich braucht ein Konto auch einen Inhaber. Das Attribut bzw. die Eigenschaft für den Inhaber ist eine Referenz auf eine andere Klasse, nämlich die Kontoinhaber-Klasse. Verändert werden die Attribute über die Methoden. So kann man beispielsweise den "Kontostand" nur mittels der Methoden "Einzahlen" und "Auszahlen" verändern.
Eine Klasse ist ein abstrakter Oberbegriff für die Beschreibung der gemeinsamen Struktur und des gemeinsamen Verhaltens von realen Objekten (Klassifizierung). Reale Objekte werden auf die für die Software wichtigen Merkmale abstrahiert. Die Klasse dient als Bauplan zur Abbildung von realen Objekten in Software-Objekte, die sogenannten Instanzen. Die Klasse fasst hierfür notwendige Eigenschaften (Attribute) und zur Manipulation der Eigenschaften notwendige Methoden zusammen. Klassen stehen häufig in Beziehung zueinander. Man hat beispielsweise eine Oberklasse (Kuchen), und aus dieser leitet sich eine andere Klasse ab (Erdbeerkuchen). Diese abgeleitete Klasse erbt bestimmte Eigenschaften und Methoden der Oberklasse.
Kapselung von Daten
Ein weiterer wesentlicher Vorteil der OOP besteht in der Kapselung von Daten. Der Zugriff auf Eigenschaften darf nur über Zugriffsmethoden erfolgen. Diese Methoden können Plausibilitätstests, Datentypwandlungen oder beliebige Berechnungen enthalten, und sie (oder ,,nur'' sie) besitzen ,,Informationen'' über die eigentliche Implementierung.

Im nächsten Abschnitt werden wir eine Roboterklasse in Python schreiben. Diese wird beispielsweise Informationen über das Baujahr und den Namen eines Roboters enthalten. Diese Informationen bezeichnet man auch als Eigenschaften bzw. Attribute einer Instanz. Es bietet sich beispielsweise an, den Namen eines Roboters als String in einem Attribut zu speichern. Datenkapselung bedeutet nun, dass wir nicht direkt auf diesen String zugreifen können. Wir müssen beispielsweise eine Methode ,,HoleNamen()'' aufrufen, um den Namen eines Roboters zu erhalten.
Das Prinzip der Datenkapselung kann man auch schön am Modell der Kontenklasse sehen. Die Methode zum Setzen des Geburtsdatums kann beispielsweise prüfen, ob das Datum korrekt ist: So kann man abfangen, wenn jemand wegen eines Tippfehlers ein Datum in der Zukunft angibt. Man könnte auch generell prüfen ob sich die Angabe innerhalb eines bestimmten Rahmens bewegt. So soll beispielsweise ein Girokonto für Kinder unter 14 nicht möglich sein oder Anlagen von Neukunden über 100 Jahre gelten als extrem unwahrscheinlich. ''
Vererbung
Ausgehend von einer allgemeinen Roboterklasse, die lediglich einen Namen und ein Baujahr kennt, könnten wir uns weitere Roboterklassen definieren wie beispielsweise "Industrieroboter", die stationär an einem Fließband eingesetzt werden können, bewegliche Roboter mit Rädern, Beinen oder Raupen und so weiter. Jede dieser Klassen erbt dann von der Basisklasse die Möglichkeit einen Namen und ein Baujahr zu haben. Wie dies genau abläuft, werden wir im folgenden Unterkapitel kennen lernen.
Bei der Konto-Klasse kann man Klassen wie Sparkonto und Girokonto definieren:

Klassen in Python
Objekte und Instanzen einer Klasse
Eine der vielen in Python integrierten Klassen ist die list-Klasse, die wir bereits häufig in unseren Übungen und Beispielen benutzt hatten. Die list-Klasse stellt eine Fülle von Methoden zur Verfügung, mit deren Hilfe wir zum Beispiel Listen aufbauen, Elemente anschauen, verändern und entfernen können:
x = [3, 6, 9]
y = [45, "abc"]
print(x[1])
x[1] = 99
x.append(42)
last = y.pop()
print(last)
Die Variablen x und y bezeichnen zwei Instanzen der list-Klasse. Vereinfacht haben wir bisher gesagt ,,x und y sind Listen''. Im Folgenden werden wir die Begriffe ,,Objekt'' und ,,Instanz'' synonym benutzen, wie dies auch in anderen Einführungen üblich ist.1
Kapselung von Daten und Methoden¶
pop() und append() aus dem obigen Beispiel sind Methoden der list-Klasse. Wir müssen nicht wissen, wie eine Liste intern im Speicher organisiert ist, um sie über ihre öffentliche Schnittstelle zu benutzen. Genau darin liegt ein wichtiger Aspekt der Kapselung: Implementierungsdetails können verborgen oder austauschbar bleiben, während Benutzerinnen und Benutzer mit einer stabilen API arbeiten. Python erzwingt diese Trennung nicht durch strikte Zugriffssperren; Konventionen, Properties und klar definierte Schnittstellen spielen deshalb eine besondere Rolle.
Eine minimale Klasse in Python¶

Die wichtigsten Begriffe der objektorientierten Programmierung und Ihrer Umsetzung in Python werden wir im Folgenden an einem Beispiel ,,Roboterklasse'' demonstrieren. Wir beginnen mit einer einfachen Klasse in Python, die wir "Roboter" nennen.
class Roboter:
pass
An diesem Beispiel können wir den grundlegenden syntaktischen Aufbau einer Klasse erkennen: Eine Klasse besteht aus zwei Teilen: dem Kopf und dem Körper.
Der Kopf besteht meist nur aus einer Zeile:
das Schlüsselwort class, gefolgt von einem Leerzeichen, einem beliebigen Namen, - in unserem Fall Roboter - einer kommaseparierten Auflistung von Oberklassen in Klammern und als letztes Zeichen ein Doppelpunkt. Gibt es keine Oberklassen, entfällt die Angabe der Oberklassen und der Klammern. Prinzipiell kann auch ein leeres Klammernpaar vor dem Doppelpunkt stehen. Das Klammernpaar mit der Auflistung der Oberklassen brauchen Sie zum jetzigen Zeitpunkt noch nicht zu verstehen, da wir erst später darauf eingehen!
Der Körper einer Klasse besteht aus einer eingerückten Folge von Anweisungen die wie in unserem Beispiel auch nur aus einer einzigen pass-Anweisung bestehen kann.
Damit haben wir bereits eine einfache Python-Klasse mit dem Namen Roboter definiert. Wir können diese Klasse auch benutzen:
class Roboter:
pass
x = Roboter()
y = Roboter()
y2 = y
print(y == y2)
print(y == x)
Wir haben in obigem Beispiel zwei verschiedene Roboter x und y geschaffen. Außerdem haben wir mit y2 = y ein Alias y2 für y erzeugt. Dabei handelt es sich nur um einen weiteren Namen für das gleiche Objekt, also um eine Referenz.
Eigenschaften und Attribute
Unsere Roboter haben keinerlei Eigenschaften. Noch nicht einmal einen Namen, wie dies für ,,ordentliche'' Roboter üblich ist. Als weitere Eigenschaften wären beispielsweise eine Typbezeichnung, Baujahr und so weiter denkbar. Eigenschaften werden in der objektorientierten Programmierung als Attribute bezeichnet. 2
Einer Instanz kann man beliebige Attributnamen zuordnen. Sie werden mit einem Punkt an den Namen der Instanz angeschlossen. Im Folgenden erzeugen wir dynamisch Attribute für den Roboternamen und das Roboterbaujahr. Bitte beachten Sie, dass dies noch nichts mit den eigentlichen Attributen zu tun hat, wie wir sie in Klassen verwenden werden:
class Roboter:
pass
x = Roboter()
y = Roboter()
x.name = "Marvin"
x.baujahr = 1979
y.name = "Caliban"
y.baujahr = 1993
print(x.name)
Attribute können übrigens auch dem Klassenobjekt selbst oder -- unabhängig von der OOP -- Funktionen zugeordnet werden, wie wir im Folgenden sehen:
class Roboter:
pass
Roboter.number = 1000
print(Roboter.number)
def f(x):
return 42
f.color = "red" # was immer es bedeuten soll, einer Funktion ein Farbattribut zuzuordnen :-)
print(f.color)
# unsere Funktion ist davon aber nicht betroffen:
print(f(10))
Attribute bei Funktionen können zum Beispiel als Ersatz für statische Funktionsvariablen, wie manche Sie von C, C++ oder Java her kennen, benutzt werden. Python kennt keine statischen Funktionsvariablen!
In der folgenden Funktion wird das Attribut "zaehler" benutzt, um zu zählen, wie oft die Funktion aufgerufen wird:
def f(x):
if hasattr(f, "counter"): # Alternativ: if "counter" in dir(f):
f.counter += 1
else:
f.counter = 0
return x + 3
for i in range(10):
f(i)
print(f.counter)
Noch ein Detail, was zum jetzigen Zeitpunkt noch nicht so wichtig ist. Sie können also gerne mit dem nächsten Unterabschnitt "Methoden" weiter machen.
Die Objekte der meisten Klassen haben ein Attributdictionary dict, in dem die Attribute mit ihren Werten gespeichert werden, wie wir es im nächsten Beispiel sehen.
class Robot:
pass
x = Robot()
x.name = "Marvin"
x.age = 5
x.__dict__
Die dynamische Erzeugung von Attributen für Instanzen sehen manche als Segen und andere als Fluch an. Attribute, die den Zustand einer Instanz beschreiben, werden üblicherweise bereits durch die Klassendefinition beziehungsweise in __init__ angelegt. So haben unsere Roboter beispielsweise verschiedene Namen und Baujahre.
Methoden¶
Namenskonvention: In aktuellem Python-Code werden Methodennamen nach PEP 8 normalerweise in snake_case geschrieben, also beispielsweise sage_hallo(). Die historisch gewachsenen Beispiele dieses Kapitels verwenden teilweise noch Namen wie SageHallo(); die Python-Mechanik ist davon unabhängig.

Im Folgenden zeigen wir, wie man Methoden in einer Klasse definiert. Dazu erweitern wir unsere Roboterklasse um eine Methode SageHallo.
Eine Methode unterscheidet sich äußerlich nur in zwei Aspekten von einer Funktion:
- Sie ist eine Funktion, die innerhalb einer
class-Definition definiert ist. - Der erste Parameter einer gewöhnlichen Instanzmethode ist eine Referenz auf die Instanz, von der sie aufgerufen wird. Diese Referenz heißt konventionell
self.
Prinzipiell könnte man einen anderen Parameternamen wählen; self ist jedoch die etablierte Python-Konvention.
Wir erweitern nun unsere Roboterklasse um eine Methode SageHallo, die beim Aufruf einfach nur „Hallo“ schreibt:
class Roboter:
def SageHallo(self):
print("Hallo")
x = Roboter()
x.SageHallo()
Wir sehen im Code, dass der Parameter ,,self'' nur bei der Definition einer Methode erscheint. Beim Aufruf wird er nicht angegeben. Im Vergleich zu Funktionsaufrufen ist das zunächst einmal befremdlich, d.h. wir definieren eine Methode mit einem Paramter ,,self'' und rufen sie scheinbar ohne Parameter auf. Aber wenn wir genauer auf den Aufruf schauen, sehen wir, dass wir ja nicht nur SageHallo() aufrufen, sondern dass die Instanz x vor dem Punkt erscheint. Darin liegt das Geheimnis: Es ist gewissermaßen so, als hätten wir SageHallo(x) aufgerufen. In anderen Worten, wir übergeben eine Referenz auf die Instanz x an self.
Zum weiteren Verständnis: Eigentlich müsste man die Methode einer Klasse über den Klassennamen aufrufen. In diesem Fall wird die Instanz als Argument übergeben, also Roboter.SageHallo(x). Weil dies aber zum einen besonders unhandlich ist und außerdem den nicht üblichen Gepflogenheiten in der OOP entspricht, bindet Python alle Methoden automatisch an die Klasseninstanzen.
Instanzvariablen
Wir wollen nun unsere Methode SageHallo so ändern, dass sie sich mit ,,Hallo, mein Name ist Marvin'' meldet, wenn der Roboter ,,Marvin'' heißt. Damit sind wir wieder zurück bei den Instanzattributen. Denn eine Instanz muss sich ihren Namen merken können. In unserem anfänglichen Beispiel definierten wir Attribute außerhalb der Klassendefinition mit Anweisungen der Art x.name = "Marvin", x.baujahr = 1979 und y.name = "Caliban". Wir können gewissermaßen das Gleiche innerhalb einer Klassendefinition in den Methoden tun. Allerdings haben wir dort natürlich keine Ahnung von dem Instanznamen, also x oder y. Wir müssen diesen Namen jedoch nicht wissen, da es sich ja beim formalen Parameter self um eine Referenz auf die aktuelle Instanz handelt. Mit diesem Wissen schreiben wir nun weitere Funktionen, mit denen wir unseren Roboter mit Namen (SetzeNamen) und Baujahr (SetzeBaujahr) versehen können:
class Roboter:
def SageHallo(self):
print("Hallo, mein Name ist " + self.name)
def SetzeNamen(self, name):
self.name = name
def SetzeBaujahr(self, baujahr):
self.baujahr = baujahr
x = Roboter()
x.SetzeNamen("Marvin")
x.SetzeBaujahr(1979)
y = Roboter()
y.SetzeNamen("Caliban")
y.SetzeBaujahr(1993)
x.SageHallo()
y.SageHallo()
Zu der Methode SetzeNamen gibt es noch Folgendes zu sagen, was auch zwei häufig gestellte Fragen beantwortet:
- Ja, statt "self" darf man irgendeinen beliebigen Namen verwenden, so auch beispielsweise "this". Man sollte dies jedoch nicht tun, weil man damit eine Python-Konvention verletzt. Außerdem werden andere Programmierer möglicherweise Schwierigkeiten haben, den Code zu verstehen. Manche Entwicklungsumgebungen, wie beispielsweise eclipse, geben auch eine Warnung, wenn man sich nicht an die diese Konvention hält.
- Der Attributnamen muss nicht gleich dem Namen des formalen Parameters sein. Häufig hat er jedoch den gleichen Namen. Wir hätten den Parameter auch beispielsweise ,,n'' nennen können.
Also ist auch der folgende Python-Code lauffähig, aber keinesfalls empfehlenswert:
class Roboter:
def SageHallo(self):
print("Hallo, mein Name ist " + self.name)
def SetzeNamen(this, n):
this.name = n
def SetzeBaujahr(self, baujahr):
self.baujahr = baujahr
x = Roboter()
x.SetzeNamen("Marvin")
x.SetzeBaujahr(1979)
y = Roboter()
y.SetzeNamen("Caliban")
y.SetzeBaujahr(1993)
x.SageHallo()
y.SageHallo()
Besonders hässlich an obigem Code ist, dass wir einmal this und einmal self verwendet haben, also dass wir nicht konsequent in unser Namensgebung waren.
Zum Stil gibt es auch noch etwas zu sagen: Laut PEP8, dem offiziellen ,,Style-Guide'', gilt: ,,Methodendefinitionen innerhalb einer Klasse werden durch eine einzelne Leerzeile getrennt.'' (Im englischen Original: ,,Method definitions inside a class are separated by a single blank line.'')
Dies wird aber in vielen Fällen nicht eingehalten. Selbst die offizielle Python-Dokumentation unter python.org ist hier nicht eindeutig! Wir werden uns im Folgenden auch nicht immer an diese Konvention halten, vor allen Dingen um Platz zu sparen.
Aber neben Stil und der Namensgebung für den ersten Parameter gibt es noch ein viel schwerwiegenderes Problem. Wenn wir einen Roboter neu schaffen, müssen wir jedesmal drei Anweisungen durchführen. In unserem Beispiel sind das, die Instanziierung mittels x = Roboter(), die Namensgebung x.SetzeNamen("Marvin") und das Setzen des Baujahres x.SetzeBaujahr(1979). Dieses Vorgehen ist umständlich, fehlerträchtig und vor allen Dingen entspricht es nicht dem üblichen Vorgehen in der OOP.
Die ```__init__```-Methode
Wir wollen die Attribute sofort nach der Erzeugung einer Instanz definieren. __init__ ist eine
Methode, die unmittelbar und automatisch nach der Erzeugung einer Instanz aufgerufen wird.
Dieser Name ist festgelegt und kann nicht frei gewählt werden! __init__
gehört zu den sogenannten magischen Methoden, von denen wir in den folgenden Kapiteln noch
weitere kennenlernen werden. Die __init__-Methode dient der Initialisierung einer Instanz. Python
besitzt keinen expliziten Konstruktor bzw. Destruktor, wie man sie in Java oder C++ kennt. Der
eigentliche Konstruktor wird implizit von Python gestartet und __init__ dient, wie der Name
andeutet der Initialisierung der Attribute. Die __init__-Methode wird jedoch unmittelbar nach
dem eigentlichen Konstruktor gestartet, und dadurch entsteht der Eindruck, als handele es sich
um einen Konstruktor. Die __init__-Methode kann an beliebiger Stelle in der Klassendefinition
stehen, sollte aber nach Konvention immer die erste Methode direkt unter dem Klassenheader sein.
class A:
def __init__(self):
print("__init__ wurde ausgeführt!")
x = A()
Wir schreiben nun für unsere Beispielklasse eine Initialisierungsmethode __init__, um bei der
Instanzierung den Namen und das Baujahr setzen bzw.
übergeben zu können, also x = Roboter("Marvin", 1979).
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
def SageHallo(self):
print("Hallo, mein Name ist " + self.name)
def NeuerName(self, name):
self.name = name
def NeuesBaujahr(self, baujahr):
self.baujahr = baujahr
x = Roboter("Marvin", 1979)
y = Roboter("Caliban", 1993)
x.