Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Objektorientierte Programmierung

Objektorientierte vs. Prozeduale Programmierung

Stellen wir uns vor, wir möchten ein Programm für die Universitätsbibliothek schreiben, das Studierende und verfügbare Lehrbücher organisiert.

Zunächst betrachten wir, wie dies im prozeduralen Programmierstil aussehen könnte.

Eigenschaften von Studierenden könnten in einer Liste gespeichert werden:

# 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]]

Man könnte nun freie Funktionen definieren, die mit diesen Eigenschaften zusammen einen logischen Programmablauf realisieren:

def celebrate_birthday(student):
    student[2] += 1

celebrate_birthday(lisa)
print(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

Hier wird das Alter des Studierenden korrekt erhöht, aber dieselbe Funktion könnte die Regalnummer eines Buchs ändern – was logisch unsinnig ist. 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.

Klassen, Methoden und Attribute

Erstellen eigener Klassen

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)

Hier ist real ein Attribut und conjugate() eine Methode des Objekts c.

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 Methode, die wir hier zunächst hervorheben wollen, ist die sogenannte Init-Funktion, auch Konstruktor genannt. Diese heißt immer __init__ und nimmt als Eingabeparameter das Objekt self entgegen und zusätzlich beliebig viele weitere Parameter. Ihre Aufgabe ist es, die Attribute der Klasse zu initialisieren - entweder mit Standardwerten oder abhängig von den Eingabeparametern.

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 (in einer Methode) mit

self.<attribute>

und von außerhalb der Klasse mit

<instance>.<attribute>

zugreifen und sie sowohl lesen als auch schreiben.

Ähnlich verhält es sich bei Methoden. Innerhalb der Klasse nutzen wir

self.<method>(<param1>, <param2>, ...)

und außerhalb

<instance>.<method>(<param1>, <param2>, ...)

Beispiel: Für die Verwaltung von Studierenden könnte eine objektorientierte Lösung zunächst folgende Klasse definieren:

class student:
    
    # Constructor
    def __init__(self, name, sex, age, course="nicht immatrikuliert", 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 Attribute

    • name, sex, age werden anhand der Eingabeparameter gesetzt, mit der wir die Init-Funktion später aufrufen werden.

    • course, semester erhalten Default-Werte (course="nicht eingeschrieben" und semester=1 in der Parameterliste der Init-Funktion). Diese Werte werden verwendet, wenn der Parameter beim Aufruf weggelassen wird.

    • marks wird als leere Liste initialisiert und ist unabhängig von den Eingabeparametern.

  • Die Klasse besitzt eine Methode:

    • celebrate_birthday.

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 explizit ü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, "Mathematics", 8)
bernd = student("Bernd", "m", 28, "Mechanical Engineering")
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 Attribut
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.

Klassenattribute

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 immatrikuliert", 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:

BezeichnungBeispiel
Klassestudent
Instanzlisa, bernd
Methodestudent.celebrate_birthday()
Instanzattributself.name, self.age, ...
Klassenattributstudent.count

Auf Instanzvariablen kann man mit folgender Syntax zugreifen:

<instanz>.<locale variable>

Methoden ruft man auf mit:

<instanz>.<function>(<param1>, <param2>, ...)

Wir haben bereits spezielle Methoden kennengelernt, die nicht explizit aufgerufen werden, sondern automatisch bei bestimmten Ereignissen ausgeführt werden:

NameBezeichnungBemerkung
__init__(self, <param_list>)KonstruktorBeim Erstellen einer Instanz aufgerufen
__del__(self)DestruktorBeim Löschen einer Instanz mit del <instance> aufgerufen

Als Übung 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

Wenn ein Name wie lisa bereits auf ein bestehendes Objekt verweist, wird bei der erneuten Zuweisung ein neues Objekt erstellt. Das vorherige Objekt wird nicht mehr referenziert und Python ruft automatisch den Destruktor __del__ auf. So wird z. B. die Exmatrikulation angezeigt, wenn das alte Objekt gelöscht wird.

Spezielle Methoden

Schauen wir uns nochmals die Init-Funktion __init__ an. Diese wird nicht explizit aufgerufen, sondern automatisch bei der Initialisierung einer Klasseninstanz über student(...). Es gibt noch weitere solcher speziellen Methoden, die, falls sie in der Klasse implementiert sind, die Arbeit mit der Klasse eleganter gestalten.

Konsolenausgabe einer Instanz:

Wenn wir die Instanz lisa einfach auf der Konsole ausgeben, erhalten wir standardmäßig nur eine Speicheradresse und die Klasseninformation:

print("Das ist Lisa:", lisa)
Das ist Lisa: <__main__.student object at 0x7801dc2317f0>
lisa
<__main__.student at 0x7801dc2317f0>

Wünschenswert wäre eine schön formatierte Ausgabe mit Namen, Alter, Geschlecht etc.

Wir erweitern die Klasse um Methoden, die die Ausgabe steuern:

class student(student):
    
    # Interne Methode für String-Repräsentation
    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)"
    
    # Aufruf bei print(<instanz>)
    def __str__(self):
        return self.__to_string__()
    
    # Aufruf bei direktem Konsolen-Ausdruck <instanz>
    def __repr__(self):
        return self.__to_string__()
  • __str__ wird bei print(<instanz>) aufgerufen.

  • __repr__ wird bei direkter Eingabe der Instanz in der Konsole ausgeführt.

Die eigentliche String-Konvertierung ist in __to_string__ ausgelagert, um Code-Duplikation zu vermeiden.

Nach dieser Modifikation erhalten wir die gewünschten Ausgaben auf der Konsole:

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)

Methoden mit doppeltem Unterstrich (__to_string__) sind privat, d.h. sie sollen nur innerhalb der Klasse verwendet werden. Zwar kann man von außen lisa.__to_string__() aufrufen, dies gilt jedoch als schlechte Praxis.

Vergleichsoperationen:

Ferner sind Vergleichsoperationen interessant. Wir möchten möglicherweise:

  • Personen nach Namen sortieren (<)

  • doppelt angelegte Instanzen erkennen (==)

Damit solche Operationen funktionieren, müssen die entsprechenden Methoden in der Klasse definiert werden. Für die Vergleichsoperatoren implementiert man:

  • __lt__(self, other) – “less than” (<)

  • __le__(self, other) – “less or equal” (<=)

  • __eq__(self, other) – “equal” (==)

Alle diese Funktionen müssen boolsche Werte zurückgeben, also True, wenn der Vergleich zutrifft, und False, wenn nicht.

Für unsere student-Klasse könnte eine Implementierung wie folgt aussehen:

class student(student):
    
    # Operator <
    def __lt__(self, other):
        return self.name < other.name    
    
    # Operator <=
    def __le__(self, other):
        return self.name <= other.name
    
    # Operator ==
    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

Da wir __lt__ und __le__ definiert haben, können wir automatisch auch > bzw. >= verwenden.

Sobald eine Vergleichsoperation implementiert ist, kann man Listen von Instanzen nach Name sortieren:

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)]

Dank der vorher implementierten __str__- bzw. __repr__-Methoden wird bei der Ausgabe der Liste eine gut lesbare Darstellung der Studierenden verwendet.

Überladung von Operatoren

Auch die üblichen Rechenoperationen lassen sich für eigene Klassen definieren. Wie schon im Kapitel Lineare Algebra mit NumPy gesehen, sind Addition, Subtraktion, Multiplikation und Division für Instanzen vom Typ numpy.ndarray implementiert.

Für eigene Klassen kann man diese Operationen durch spezielle Methoden realisieren. Für die Addition (+) implementiert man z.B. die Methode

__add__(self, other)

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 zwei komplexe Zahlen angelegt, addiert und das Ergebnis auf der Konsole ausgegeben:

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

In der folgenden Tabelle sind weitere vordefinierte Methoden zur Operatorüberladung zusammengefasst:

OperatorMethode
+__add__(self,other)
-__sub__(self,other)
*__mul__(self,other)
/__div__(self,other)
**__pow__(self,other)
==__eq__(self, other)
>=__ge__(self, other)
>__gt__(self, other)
<=__le__(self, other)
<__lt__(self, other)

Vererbung

Bei der Vererbung übernimmt eine Klasse die Eigenschaften und Methoden einer anderen Klasse und ergänzt diese gegebenenfalls durch weitere Attribute und Methoden. Dies ist besonders sinnvoll, wenn mehrere Klassen viele gemeinsame Eigenschaften teilen.

Die Syntax zur Ableitung einer Klasse lautet:

class <sub_class>(<base_class>):
    [...]

Als Beispiel betrachten wir

class animal:
    # Klassenattribute
    description = "unbekanntes Tier"
    region = "Irgendwo"
            
    # Konsolenausgabe für alle Tiere
    def __str__(self):
        return "Ich bin ein "+self.description+" und mein Lebensraum ist "+self.region+".";
    
class fish(animal):
    # Klassenattribute
    description = "Fisch"
    region = "Wasser";
    
    # Konstruktor für Fische
    def __init__(self, color):        
        # Instanzattribute
        self.color = color
        
class mammal(animal):
    # Klassenattribute
    description = "Säugetier"
    region = "Land"
    
    def __init__(self, nr_legs):
        # Instanzattribute
        self.nr_legs = nr_legs

# Hauptprogramm beginnt hier
carp = fish("blau")
print(carp)

monkey = mammal(2)
print(monkey)
Ich bin ein Fisch und mein Lebensraum ist Wasser.
Ich bin ein Säugetier und mein Lebensraum ist Land.
  • Die Klassenattribute description und region existieren sowohl in der Basisklasse animal als auch in den abgeleiteten Klassen fish und mammal.

  • Für Objekte der abgeleiteten Klassen nehmen die Klassenattribute stets den Wert der abgeleiteten Klasse an.

  • Die Methode __str__ muss nur einmal in der Basisklasse definiert werden, um für alle Objekte gültig zu sein.

Überschreiben von Methoden (Override):

Wenn wir die Methode __str__ in der abgeleiteten Klasse erneut implementieren, überschreibt diese die Implementierung der Basisklasse für Objekte dieser abgeleiteten Klasse. Wir können dennoch mit animal.__str__ explizit auf die Basisklassen-Implementierung zugreifen:

class mammal(animal):
    
    description = "Säugetier"
    region = "Land"
    
    def __init__(self, nr_legs):
        self.nr_legs = nr_legs
        
    def __str__(self):
        return animal.__str__(self)+" Ich habe "+str(self.nr_legs)+" Beine."
    
human = mammal(2)
print(human)
Ich bin ein Säugetier und mein Lebensraum ist Land. Ich habe 2 Beine.

Python erlaubt natürlich auch mehrere Ebenen der Vererbung.
Wir könnten von unserer Klasse für Säugetiere weitere Klassen ableiten, z.B. für Nagetiere, Huftiere, etc.

Darüber hinaus ist auch Mehrfachvererbung möglich. Möchten wir eine Klasse von zwei oder mehreren Basisklassen ableiten, verwenden wir die Syntax:

class <sub_class>(<base_class_1>, <base_class_2>[, ...]):
    [...]

Die neue Klasse erbt dann alle Eigenschaften und Methoden von <base_class_1> und <base_class_2>. Kritisch wird es nur, wenn beide Basisklassen Methoden mit gleichem Namen aber unterschiedlicher Implementierung bereitstellen:

class horse:
    def __init__(self):
        print("Horse-Konstruktor aufgerufen")
    def output(self):
        print("Hüüüü")
class human:
    def __init__(self):
        print("Human-Konstruktor aufgerufen")
    def output(self):
        print("Arghh")

# Klasse für ein Mischwesen
class centaur(horse, human):
    pass

a = centaur()
a.output()
Horse-Konstruktor aufgerufen
Hüüüü
  • centaur erbt von horse und human.

  • Beim Aufruf von a.output() wird die Methode der ersten Basisklasse (horse) verwendet.

  • Python verwendet hier die sogenannte Method Resolution Order (MRO), d.h. die Reihenfolge, in der Basisklassen durchsucht werden, um eine Methode zu finden.

  • Problematisch wird es nur, wenn Konstruktoren (__init__) komplexe Logik enthalten; in diesem Fall muss man häufig die Basisklassen explizit aufrufen, z.B. horse.__init__(self) oder super().__init__().

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. Dafür müssen zwei Methoden implementiert werden:

  • __iter__(self): Initialisiert den Iterator und gibt das aktuelle Objekt selbst zurück.

  • __next__(self): Gibt das Nachfolgeelement der Iteration zurück oder wirft die Exception StopIteration

Beispiel: Lottozahlen 6 aus 49

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 beiden Funktionen reichen aus, um das Objekt in einer for-Schleife zu verwenden:

for i in LotteryNumbers():
    print("Die Zahl", i, "wurde gezogen.")
Die Zahl 32 wurde gezogen.
Die Zahl 32 wurde gezogen.
Die Zahl 12 wurde gezogen.
Die Zahl 13 wurde gezogen.
Die Zahl 16 wurde gezogen.
Die Zahl 35 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 StopIteration:
    print("Alle Zahlen wurden gezogen")
Lottozahl 33
Lottozahl 43
Lottozahl 14
Lottozahl 15
Lottozahl 26
Lottozahl 11
Alle Zahlen wurden gezogen

Das Konstrukt bestehend aus try und except dient zum Exception-Handling. Python versucht den Block unter try auszuführen. Sobald eine Exception auftritt (hier StopIteration in LotteryNumbers.__next__), wird der except-Block ausgeführt.

Code-Dokumentation

Damit es potenziellen Anwendern unserer selbst programmierten Klassen einfacher fällt, ist eine ordentliche Code-Dokumentation sehr hilfreich.
Wir können die Klasse selbst sowie alle Methoden mit Docstrings versehen, also Kommentaren der Form:

"""
[Kommentar]
...
[Kommentar]
"""

Dieser Text erscheint dann im Hilfetext im Jupyter Lab, wenn wir mit ? nach der Dokumentation einer Klasse oder Methode fragen.

Beispiel: Dokumentierte 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.