5. Objektorientierte Programmierung#
5.1. Objektorientierte vs. Prozeduale Programmierung#
Angenommen wir möchten ein Programm für die Universitätsbibliothek schreiben, welche die registrierten Studierenden sowie die in der Bibliothek verfügbaren Lehrbücher organisiert. Schaue wir uns zunächst an, wie ein solches Programm in einem prozedualen Programmierstil aufgebaut sein könnte.
Die Studierenden beispielsweise besitzen verschiedene Eigenschaften, die wir in einer vorher definierten Reihenfolge in einer Liste ablegen könnten:
# Student: Name, Geschlecht, Alter, Studiengang, Semester, Noten
lisa = ["Lisa", "w", 23, "Mathematik", 8, [1,3,2,2]]
bernd = ["Bernd", "m", 25, "Maschinenbau", 6, [3,3,4]]
Für Studierende könnte man nun freie Funktionen definieren, die mit diesen Eigenschaften zusammen einen logischen Programmablauf realisieren:
def celebrate_birthday(student):
student[2] += 1
celebrate_birthday(lisa)
lisa
['Lisa', 'w', 24, 'Mathematik', 8, [1, 3, 2, 2]]
Dieser Programmierstil birgt allerdings einige Nachteile. Wenn wir im späteren Programmcode den Studiengang eines Studierenden wechseln wollen, werden wir uns noch daran erinnern, dass dieser an 4. Stelle der Liste steht? Außerdem könnte die freie Funktion celebrate_birthday
auch auf alle anderen Listen mit mindestens 3 Elementen und einer Zahl an dritte Stelle angewendet werden. Unser Bibliotheksprogramm könnte die Lehrbücher beispielsweise wie folgt speichern
# Lehrbuch: Author, Titel, Regalstandort, Ausleihstatus
lehrbuch1 = ["Harro Heuser", "Lehrbuch der Analysis 1", 4, False]
und natürlich macht es keinen Sinn den Geburtstag einer Lehrbuchs zu feiern, dennoch würde unser Programm dies zulassen. Dann wird allerdings das Buch in das nächste Regal verschoben:
print("Unser Buch liegt in Regal", lehrbuch1[2])
celebrate_birthday(lehrbuch1)
print("Unser Buch liegt in Regal", lehrbuch1[2])
Unser Buch liegt in Regal 4
Unser Buch liegt in Regal 5
Sinnvoll wäre es hier die Funktion celebrate_birthday
direkt an Studierende zu koppeln. Im folgenden Abschnitt schauen wir uns eine objektorientierte Umsetzung unseres Programms an.
5.2. Klassen, Methoden und Attribute#
Die objektorientierte Programmierung ist ein Programmierstil bei dem spezifische Eigenschaften und darauf anwendbare Operationen in individuellen Objekten gebündelt.
So ein Objekt ist beispielsweise ein Student oder eine Studentin, ausgestattet mit individuellen Eigenschaften wie Name, Geschlecht, Alter, Studiengang, Fachsemester, Notendurchschnitt. Eine Studentin führt gewisse Aktionen aus, beispielsweise Altern, Atmen, Lernen, eine Prüfung ablegen, etc..
Die Idee der Objektorientierten Programmierung ist es, die Klassifizierung Student/in mit einem eigens dafür vorgesehenen Datentyp, einer sogenannten Klasse, zu assozieren. Diese Klasse soll die vorher festgelegten Eigenschaften und Operationen bereitstellen. Wir erinnern uns vielleicht noch an die vorangegangenen Kapitel. Wir hatten bereits mit einer Klasse für komplexe Zahlen gearbeitet.
import cmath
c = 1+3j
# Abfrage eines Attributs
print("Realteil von c ist", c.real)
# Ausführen einer Methode
print("Die konjugiert komplexe lautet", c.conjugate())
Realteil von c ist 1.0
Die konjugiert komplexe lautet (1-3j)
Attribute und Methoden sind bereits die wichtigsten Bestandteile einer Klasse. Wir wollen nun lernen eigene Klassen zu implementieren. Die allgemeine Syntax sieht wie folgt aus:
class <class_name>:
def __init__(self, <param1>, <param2>, ...):
self.<attribute_1> = ...
self.<attribute_2> = ...
def <method_1>(self, [parameter_list]):
[do something]
return <result>
def <method_2>(self, [parameter_list]):
[do something]
return <result>
Die Klassendefinition beginnt mit dem Schlüsselwort class
, gefolgt vom Namen unserer Klasse. Innerhalb der Klassendefinition werden nun Funktionen definiert, die wir in diesem Kontext als Methoden bezeichnen. Methoden sind stets an eine Instanz (Objekt) dieser Klasse gekoppelt. Eine spezielle Funktion, die wir hier zunächst hervorheben wollen ist die sogenannte Init-Funktion, auch Konstruktor genannt. Diese trägt immer den Namen __init__
und nimmt als Eingabeparameter das Objekt self
entgegen, sowie beliebig viele weitere Parameter. Die Aufgabe der Init-Funktion ist es die Attribute der Klasse zu initialisieren. Die Attribute können beispielsweise auf einen Standardwert gesetzt werden, oder in Abhängigkeit der Eingabeparameter initialisiert werden.
Wir können anschließend im Hauptprogramm sogenannte Instanzen unserer Klasse erzeugen mit
<instance> = <class_name>(<param1>, <param2>, ...)
Auf die Attribute der Klasse können wir innerhalb der Klasse (also in der Implementierung einer Methode) mit
self.<variable>
und von außerhalb der Klasse mit
<instance>.<variable>
zugreifen und diese sowohl lesen als auch schreiben. Ähnlich verhält es sich bei dem Aufruf von Methoden. Innerhalb der Klasse nutzen wir
self.<method>(<param1>, <param2>, ...)
und außerhalb
<instance>.<method>(<param1>, <param2>, ...)
Für die Verwaltung von Studierenden wäre für eine objektorientierte Lösung zunächst die folgende Definition einer Klasse sinnvoll:
class student:
# Constructor
def __init__(self, name, sex, age, course="nicht eingeschrieben", semester=1):
print(name, "immatrikuliert")
self.name = name
self.sex = sex
self.age = age
self.course = course
self.semester = semester
self.marks = []
# Increases and returns age of a student
def celebrate_birthday(self):
print(self.name, "feiert Geburtstag")
self.age += 1
return self.age
Dieser Code erfordert eine ausführlichere Erläuterung:
Wir haben eine Klasse namens
student
definiert.Die Klasse besitzt die Attribute
name
,sex
,age
,course
,semester
,marks
, welche in der Init-Funktion zunächst initialisiert werden. Die Init-Funktion initialisiert die Attributename
,sex
,age
anhand der Eingabeparameter mit der wir die Init-Funktion später aufrufen werden. Für die Attributecourse
undsemester
wurden Default-Werte angegeben (beachte die Schreibweisecourse="nicht eingeschrieben"
undsemester=1
in der Parameterliste der Init-Funktion). Diese Default-Werte werden verwendet, wenn die Parameter beim Aufruf dieser Methode weggelassen werden. Das Attributmarks
wurde einfach mit einem sinnvollen Default-Wert initialisiert - einer leeren Liste - und ist unabhängig von den Eingabeparametern.Die Klasse besitzt eine Methode namens
celebrate_birthday
. Diese Methode erhöhrt das Attributage
um 1 und gibt das neue Alter zurück.
Beachte: Bei allen Methoden einer Klasse ist der erste Funktionsparameter immer self
, also der Zeiger auf die Instanz selbst. Beim Aufruf einer Funktion wird dieses Objekt nicht mit übergeben.
Schauen wir uns nun an, wie wir mit unserer Klasse arbeiten können. Wir erzeugen zunächst 2 Instanzen dieser Klasse:
# Instanzen der Klasse student erzeugen (ruft Init-Funktion auf)
lisa = student("Lisa", "w", 23, "Mathematik", 8)
bernd = student("Bernd", "m", 28, "Maschinenbau")
Lisa immatrikuliert
Bernd immatrikuliert
An der Konsolenausgabe erkennen wir, dass tatsächlich die Init-Funktion aufgerufen wurde. Somit sind auch die Attribute sinnvoll initialisiert. Wir können beispielsweise Lisa’s Alter abfragen, also auf ein Attribut zugreifen:
# Zugriff auf Klassenvariable
print("Lisa ist", lisa.age, "Jahre alt")
Lisa ist 23 Jahre alt
Um eine Methode auf Lisa aufzurufen nutzen wir
# Aufrufen einer Methode
new_age = lisa.celebrate_birthday()
print("Lisa ist jetzt", new_age, "Jahre alt")
Lisa feiert Geburtstag
Lisa ist jetzt 24 Jahre alt
Dies waren schon die wichtigsten Konzepte zu Klassen. Wir sollten an diesem Punkt Attribute und Methoden, sowie den Sinn der Init-Funktion verstanden haben.
Übungsaufgabe
Schreibe eine Klasse book
, welche ein Lehrbuch repräsentieren soll. Die Klasse soll in der Init-Funktion Attribute wie Author, Titel, Regalstandort und Ausleihstatus anhand entsprechender Eingabeparameter initialisieren. Standardmäßig ist ein Buch nicht ausgeliehen. Implementiere außerdem eine Methode rent
, welche im Fall einer Ausleihe aufgerufen wird und die entsprechenden Attribute modifiert, sowie eine Methode is_rent
, welche abfragt, ob ein Buch verliehen ist. In diesem Fall soll True
zurückgegeben werden, andernfalls False
.
Einige weitere Begriffe müssen wir noch einführen. Das, was wir bisher als Attribut bezeichnet haben, sollte eigentlich den Begriff Instanzattribut tragen, denn es ist an eine bestimmte Instanz unserer Klasse gekoppelt. Eine weitere Art von Attribut ist das sogenannte Klassenattribut (auch Klassenvariable). Ein Klassenattribut ist eine Variable, die sich alle Instanzen einer Klasse teilen. Ein Klassenaatribut wird im Rumpf einer Klasse definiert
class <class_name>:
<class_attribute> = <value>
und sowohl innerhalb als auch außerhalb der Klasse mit
<value> = <class_name>.<class_attribute>
<class_name>.<class_attribute> = <value>
gelesen bzw. geschrieben. Wir können beispielsweise unsere student
-Klasse um ein Klassenattribut namens count
erweitern, mit dem wir später die Anzahl der immatrikulierten Studierenden zählen können. Der Counter soll in der Init-Funktion erhöhrt und im Gegenstück, der Delete-Funktion oder auch Destruktor (diese wird aufgerufen, wenn ein Objekt zerstört wird) verringert wird. Wir könnten im Jupyter-Notebook die Zelle, in der wir die Klasse student
definiert haben, editieren, oder die Klasse durch die rekursive Klassendefinition class student(student)
um eine Methode erweitern, bzw. eine bestehende Methode ersetzen:
class student(student):
# Klassenattribut definieren
count = 0
# Init-Funktion
def __init__(self, name, sex, age, course="nicht eingeschrieben", semester=1):
print(name, "immatrikuliert")
self.name = name
self.sex = sex
self.age = age
self.course = course
self.semester = semester
self.marks = []
# Klassenattribut schreiben
student.count += 1
def __del__(self):
print(self.name, "exmatrikuliert")
# Klassenattribut schreiben
student.count -= 1
Wir erzeugen wieder 2 Instanzen der Klasse student
, lesen das Klassenattribut count
, löschen einen Studenten, und lesen das Klassenattribut erneut:
lisa = student("Lisa", "w", 23, "Mathematik", 8)
bernd = student("Bernd", "m", 28, "Maschinenbau")
print("Anzahl Studierende:", student.count)
del bernd # Dies ruft die Delete-Funktion auf
print("Anzahl Studierende:", student.count)
Lisa immatrikuliert
Bernd immatrikuliert
Anzahl Studierende: 2
Bernd exmatrikuliert
Anzahl Studierende: 1
Wir fassen zunächst die gelernten Begriffe zusammen:
Bezeichnung |
Beispiel |
---|---|
Klasse |
|
Instanz |
|
Methode |
|
Instanzattribut |
|
Klassenattribut |
|
Mit der Syntax
<instanz>.<locale variable>
können wir auf die Instanzvariablen zugreifen, und mit
<instanz>.<function>(<param1>, <param2>, ...)
Methoden aufrufen.
Wir haben bereits spezielle Methoden kennengelernt, die nicht explizit aufgerufen werden, sondern eine Sonderrolle einnehmen:
Name |
Bezeichnung |
Bemerkung |
---|---|---|
|
Konstruktor |
Beim Erstellen einer Instanz aufgerufen |
|
Destruktor |
Beim Löschen einer Instanz mit |
Fügen wir nun noch einige Funktionen zu unserer Klasse hinzu:
class student(student):
# Füge Prüfungsergebnis hinzu
def add_exam(self, mark):
self.marks.append(mark)
# Berechne Notendurchschnitt
def get_average_mark(self):
nr_marks = len(self.marks)
if nr_marks > 0:
return sum(self.marks) / len(self.marks)
else:
return 0
Da wir die Klasse student
verändert haben, müssen wir die Instanz lisa
nochmal neu anlegen. Für diese Instanz können wir nun Prüfungsnoten hinzufügen und den Notendurchschnitt berechnen lassen:
lisa = student("Lisa", "w", 23, "Mathematik", 8)
lisa.add_exam(2.0)
lisa.add_exam(1.3)
avg = lisa.get_average_mark()
print(lisa.name, "hat einen Notendurchschnitt von", avg)
Lisa immatrikuliert
Lisa exmatrikuliert
Lisa hat einen Notendurchschnitt von 1.65
Interessant ist hier die Konsolenausgabe. Es wird offensichtlich Init- und Delete-Funktion von student
aufgerufen. Das liegt daran, dass der name lisa
bereits an ein Objekt vom Typ student
gebunden ist. Beim Aufruf von lisa = student(...)
wird ein neues Objekt erstellt und der Name lisa
wird an dieses gebunden. Das ursprüngliche Lisa-Objekt wird nun nicht mehr referenziert und wird von Python daher gelöscht, was einen Aufruf der Delete-Funktion zur Folge hat.
5.2.1. Spezielle Methoden#
Schauen wir uns nochmal die Init-Funktion __init__
an. Diese wird nicht explizit aufgerufen, sondern bei der Initialisierung einer Klasseninstanz über student(...)
. Es gibt noch weitere solcher Funktionen, die, falls sie in der Klasse implementiert sind, die weitere Arbeit mit der Klasse eleganter gestaltet.
Probieren wir nun mal die Instanz lisa
auf der Konsole auszugeben:
print("Das ist Lisa:", lisa)
Das ist Lisa: <__main__.student object at 0x7f32646d7d00>
lisa
<__main__.student at 0x7f32646d7d00>
Standardmäßig wird ein Text ausgegeben, der uns verrät zu welcher Klasse Lisa gehört, und an welcher Stelle des Speichers Lisa liegt. Wünschenswert wäre vielleicht eine schön formattierte Konsolenausgabe.
Dazu müssen wir die Klasse um 2 weitere versteckte Funktionen erweitern:
class student(student):
# Aufgabe einer Instanz als String
def __to_string__(self):
return self.name + ", " \
+ ("männlich" if self.sex == "m" else "weiblich") + ", " \
+ str(self.age) + " Jahre, " \
+ self.course + " (" + str(self.semester) + ". Semester)"
# Konvertierung zu String bei print
def __str__(self):
return self.__to_string__()
# Konvertierung zu String bei Standard-Konsolenausgabe
def __repr__(self):
return self.__to_string__()
Die Methode __str__
wird bei der Konsolenausgabe mit print
aufgerufen, und die Methode __repr__
, falls wir einfach nur lisa
in die Konsole eintippen. Da beide Funktionen hier das Gleiche ausgeben sollen, haben wir die eigentliche Umwandlung in einen String in die Funktion __to_string__
ausgelagert.
lisa = student("Lisa", "w", 23, "Mathematik", 8)
print("Das ist Lisa:", lisa)
Lisa immatrikuliert
Das ist Lisa: Lisa, weiblich, 23 Jahre, Mathematik (8. Semester)
lisa
Lisa, weiblich, 23 Jahre, Mathematik (8. Semester)
Wir haben bei der Methode __to_string__
auch doppelte Unterstriche vorangestellt. Dies bedeutet im Allgemeinen, dass die Methode privat ist und lediglich von anderen Mehoden der Klasse, aber nicht von außen aufgerufen werden soll. Prinzipiell ist der Aufruf lisa.__to_string()__
zwar erlaubt, sollte aber von der Programmiererin nicht verwendet werden.
Ferner sind Vergleichsoperationen interessant. Wir wollen vielleicht über Vergleiche mit <
Personen nach Namen sortieren, oder mit ==
doppelt angelegte Instanzen ermitteln.
Solche Vergleichsoperationen müssen natürlich irgendwo in unserer Klasse definiert sein. Dazu implementiert man die Funktion __lt__(self,other)
(“lt” steht für “less than”), und für die Operationen <=
noch __le__
(less or equal) und für ==
noch __eq__(self,other)
(equal):
class student(student):
# Comparison operation <
def __lt__(self, other):
return self.name < other.name
# Comparison operation <=
def __le__(self, other):
return self.name <= other.name
# Comparison operation ==
def __eq__(self, other):
return self.name == other.name
lisa = student("Lisa", "w", 23, "Mathematik", 8)
bernd = student("Bernd","m", 25, "Maschinenbau", 6)
lisa2 = student("Lisa", "w", 21, "Psychologie", 2)
print("Lisa kleiner Bernd : ", lisa < bernd)
print("Lisa kleiner/gleich Lisa : ", lisa <=bernd)
print("Lisa gleich Lisa2 : ", lisa == lisa2)
print("Lisa größer Bernd : ", lisa > bernd)
print("Lisa größer/gleich Bernd : ", lisa >= bernd)
Lisa immatrikuliert
Bernd immatrikuliert
Lisa immatrikuliert
Lisa kleiner Bernd : False
Lisa kleiner/gleich Lisa : False
Lisa gleich Lisa2 : True
Lisa größer Bernd : True
Lisa größer/gleich Bernd : True
Mit der Implementierung von __le__
bzw __lt__
können wir also die Operatoren >
bzw. >=
verwenden. Da wir nun eine Vergleichsoperation haben, ist auch der sort
-Befehl in Listen von Instanzen der Klasse student
ausführbar:
student_list = [student("Lisa", "w", 23, "Mathematik", 8),
student("Bernd", "m", 25, "Maschinenbau", 6),
student("Tom", "m", 32, "Psychologie", 22),
student("Anna", "w", 19, "Chemie", 2)]
student_list.sort()
student_list
Lisa immatrikuliert
Bernd immatrikuliert
Tom immatrikuliert
Anna immatrikuliert
[Anna, weiblich, 19 Jahre, Chemie (2. Semester),
Bernd, männlich, 25 Jahre, Maschinenbau (6. Semester),
Lisa, weiblich, 23 Jahre, Mathematik (8. Semester),
Tom, männlich, 32 Jahre, Psychologie (22. Semester)]
Auch unsere Implementierung der String-Darstellung aus __str__
wurde bei der Konsolen-Ausgabe verwendet.
5.2.2. Überladung von Operatoren#
Auch die üblichen Rechenoperationen lassen sich für eigene Klassen definieren. Wie schon im Kapitel {ref}numpy
gesehen, war die Addition, Subtraktion, Multiplikation und Division für Instanzen vom Typ numpy.ndarray
definiert. Um dies für eigene Klassen zu realisieren müssen wir beispielsweise für die +
-Operation die Methode __add__(self, other)
implementieren. Da die Addition zweier Studierender wenig Sinn macht, wechseln wir hier das Beispiel und implementieren eine eigene Klasse für komplexe Zahlen:
class my_complex:
# Init-Funktion
def __init__(self, real, imag=0.):
self.real = real
self.imag = imag
# Konsolenausgabe
def __to_string__(self):
return str(self.real) + ("+" if self.imag >= 0 else "") + str(self.imag) + "i"
def __str__(self):
return self.__to_string__()
def __repr__(self):
return self.__to_string__()
# Addition
def __add__(self, other):
return my_complex(self.real+other.real, self.imag+other.imag)
In folgendem Beispiel werden 2 komplexe Zahlen angelegt, addiert und das Ergebnis wird auf die Konsole geschrieben:
x = my_complex(1., 3.)
y = my_complex(1., -2.)
z = x + y # ruft __add__ auf
print(x, "+", y, "=", z)
1.0+3.0i + 1.0-2.0i = 2.0+1.0i
Übungsaufgabe
In der Funktion my_complex.__to_string__
haben wir erstmals eine implizite if
-Abfrage verwendet. Versuche selbst nachzuvollziehen wie diese funktioniert. Schreibe alternativ eine herkömmliche if
-Abfrage für die Ausgabe des richtigen Vorzeichens vor dem Imaginärteil.
In der folgenden Tabelle sind weitere vordefinierte Methoden zur Operatorüberladung zusammengefasst:
Operator |
Methode |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Übungsaufgabe
Vervollständige die Klasse my_complex
und implementiere, ausgenommen der Vergleichsoperatoren, alle Operatoren aus der Tabelle. Teste die Implementierung an einfachen Beispielen.
5.3. Vererbung#
Bei der Vererbung übernimmt eine Klasse die Eigenschaften und Methoden einer anderen Klasse und ergänzt diese gegebenenfalls durch weitere Eigenschaften und Methoden. Dies ist sinnvoll, wenn man mit mehreren Klassen arbeiten möchte, für die viele Eigenschaften gleich sind. Um eine Klasse von einer anderen abzuleiten nutzen wir die Syntax
class <sub_class>(<base_class>):
[...]
Als Beispiel betrachten wir
class animal:
# Klassenattribute
description = "unknown animal"
region = "somewhere"
# Konsolenausgabe für alle Tiere
def __str__(self):
return "I am a "+self.description+" and I live in/at "+self.region+".";
class fish(animal):
# Klassenattribute
description = "fish"
region = "water";
# Konstruktor für Fische
def __init__(self, color):
# Instanzattribute
self.color = color
class mammal(animal):
# Klassenattribute
description = "mammal"
region = "land"
def __init__(self, nr_legs):
# Instanzattribute
self.nr_legs = nr_legs
# Hauptprogramm beginnt hier
carp = fish("blue")
print(carp)
monkey = mammal(2)
print(monkey)
I am a fish and I live in/at water.
I am a mammal and I live in/at land.
Die Klassenattribute description
und region
sind sowohl in der Basisklasse, als auch in der abgeleiteten Klasse definiert. Diese nehmen aber stets den Wert der abgeleiteten Klasse an. Die Funktion für die Konsolenausgabe __str__
mussten wir damit nur in der Basisklasse definieren.
Wenn wir nun die __str__
-Funktion in der abgeleiteten Klasse mammal
erneut implementieren, dann wird auch diese für alle Objekte vom Typ mammal
aufgerufen und nicht die Implementierung der Basisklasse. Wir können aber mit animal.__str__
weiterhin auf die Implementierung dieser Funktion aus der Basisklasse zugreifen:
class mammal(animal):
description = "mammal"
region = "land"
def __init__(self, nr_legs):
self.nr_legs = nr_legs
def __str__(self):
return animal.__str__(self)+" I have "+str(self.nr_legs)+" legs."
human = mammal(2)
print(human)
I am a mammal and I live in/at land. I have 2 legs.
Python erlaubt natürlich auch weitere Ebenen der Vererbung. Wir könnten von unserer Klasse für Säugetiere noch weitere Klassen ableiten, für Nagetiere, Huftiere, etc.. Auch eine Mehrfachvererbung ist möglich. Möchten wir eine Klasse von 2 anderen ableiten, dann schreiben wir einfach
class <sub_class>(<base_class_1>, <base_class_2>[, ...]):
[...]
und unsere neue Klasse erbt alle Eigenschaften und Methoden von <base_class_1>
und <base_class_2>
. Kritisch wird es nur dann, wenn beide Klassen Methoden mit gleichem Namen aber unterschiedlicher Implementierung bereitstellen:
class horse:
def __init__():
print("Horse constructor called")
def output():
print("Hüüüü")
class human:
def __init__():
print("Human constructor called")
def output():
print("Arghh")
# Klasse für ein Mischwesen
class centaur(horse, human):
pass
a = centaur
a.output()
Hüüüü
5.4. Iteratoren#
Wir haben schon einige Datentypen gesehen, über die wir in einer for
-Schleife iterieren können, beispielsweise list
, tuple
, string
. Man kann auch selbst programmierte Klassen iterierbar machen. Wichtig ist hierfür die Implementierung der folgenden 2 Funktionen:
__iter__(self)
: Initialisiert den Iterator und gibt gibt das aktuelle Objekt selbst zurück.__next__(self)
: Gibt das Nachfolgeelement der Iteration zurück oder wirft die ExceptionStopIteration
Als Beispiel implementieren wir eine Klasse zur Ziehung der Lottozahlen:
import random
class LotteryNumbers:
def __iter__(self):
self.n = 0 # Initialisiere Zähler
return self # Rückgabe des aktuellen Objekts
def __next__(self):
self.n += 1 # Inkrementiere Zähler
if self.n >=7:
raise StopIteration # Abbruch nach 6 Zahlen
else:
return random.randint(1,49) # Erzeuge Zufallszahl
Diese 2 Funktionen reichen aus um mit einer for
-Schleife über dieses Objekt zu iterieren:
for i in LotteryNumbers():
print("Die Zahl", i, "wurde gezogen.")
Die Zahl 39 wurde gezogen.
Die Zahl 40 wurde gezogen.
Die Zahl 6 wurde gezogen.
Die Zahl 23 wurde gezogen.
Die Zahl 12 wurde gezogen.
Die Zahl 43 wurde gezogen.
Ein iterierbares Objekt lässt sich auch wie folgt manuell durchgehen:
numbers = iter(LotteryNumbers()) # Erzeuge Iterator
try:
print("Lottozahl", next(numbers))
print("Lottozahl", next(numbers))
print("Lottozahl", next(numbers))
print("Lottozahl", next(numbers))
print("Lottozahl", next(numbers))
print("Lottozahl", next(numbers))
print("Lottozahl", next(numbers))
print("Lottozahl", next(numbers))
except:
print("Alle Zahlen wurden gezogen")
Lottozahl 26
Lottozahl 44
Lottozahl 47
Lottozahl 1
Lottozahl 26
Lottozahl 29
Alle Zahlen wurden gezogen
Das Konstrukt bestehend aus try
und except
dient zum “Exception-Handling”. Python versucht den Block unter try
auszuführen und springt, sobald eine dieser Funktionen eine Exception wirft (beachte die Zeile raise StopIteration
in LotteryNumbers.__next(self)__
), zum except
-Block.
5.5. Code-Dokumentation#
Damit es potentielle Anwender unserer selbst programmierten Klassen einfacher haben, ist eine ordentliche Code-Dokumentation sehr hilfreich. Wir können die Klasse selbst, sowie alle Methoden mit Kommentaren der Form
"""
[Comment]
...
[Comment]
"""
versehen. Dieser Text erscheint dann im Hilfetext im Jupyter-Notebook, wenn wir mit ?
nach der Dokumentation einer Klasse oder Methode fragen. Hier ein Beispiel einer gut dokumentierten Klasse:
class Car:
"""
A car object, equipped with a model name and color.
"""
def __init__(self, model, color="gray"):
"""
Constructor
Initializes a new car object with a default name. The engine is off.
Parameters
----------
model: string
Model of the car (e.g. VW, Mercedes, ...)
color: string, optional
Color of the car (e.g. red, blue, ...). Default value: gray
"""
self.model = model
self.color = color
self.engine_on = False
def start_engine(self):
"""
start_engine(self)
Method that allows to start the engine
Example
-------
>>> mercedes = Car()
>>> mercedes.start_engine()
"""
self.engine_on = True
def engine_status(self):
"""
engine_status()
Returns the engine status.
Returns
-------
out: bool
Returns True when the engine is on or False when it is off
See Also
--------
start_engine : Method used to start the engine
"""
return engine_on
Wir bekommen nun mit
Car?
unsere Klassendokumentation angezeigt und mit
Car.engine_status?
die Dokumentation der engine_status
-Methode.