Home | Lehre | Videos | Texte | Vorträge | Software | Person | Impressum, Datenschutzerklärung | ![]()
Stand: 2025-04-02
weitgehend formuliert von ChatGPT 4, verbessert von ChatGPT 4o,
redigiert/korrigiert von Jörn Loviscach
Objektorientierte Programmierung (OOP) ist ein
Programmierparadigma
, also eine Herangehensweise zum Schreiben
und Organisieren von Programmcode. Es zielt darauf ab, Code in einer
strukturierten und intuitiven Weise zu organisieren, indem Daten und
Funktionen in Einheiten, sogenannten Objekten, zusammengefasst werden.
Im Gegensatz zum prozeduralen Programmierparadigma, wie es oft in C
verwendet wird, wo Daten und Funktionen getrennt sind, ermöglicht OOP,
dass Daten und die Funktionen, die sie manipulieren, in denselben
Einheiten enthalten sind. Diese Objekte sind wie kleine, selbstständige
Maschinen, die bestimmte Daten speichern und Operationen auf diesen
Daten ausführen können.
Dieser Ansatz hilft dabei, Programme modularer und klarer zu strukturieren. Insbesondere kann man Objekten Anweisungen geben wie in diesem Beispiel (der komplette Code folgt weiter unten):
# Erzeugung eines Kaffeemaschinen-Objekts
meine_kaffeemaschine = Kaffeemaschine('CaffeineBlaster', 2)
# Nutzung des Kaffeemaschinen-Objekts
meine_kaffeemaschine.einschalten()
meine_kaffeemaschine.wasser_auffüllen(1) # Füge 1 Liter Wasser hinzu
meine_kaffeemaschine.kaffee_brühen() # Brühe eine Tasse Kaffee
meine_kaffeemaschine.ausschalten()Ein zentraler Aspekt der OOP ist die Kapselung,
welche die Zusammenfassung von Daten (in diesem Zusammenhang Attribute
genannt) und Funktionen (in diesem Zusammenhang Methoden genannt)
innerhalb von Objekten sowie den Schutz dieser Daten vor unerlaubtem
Zugriff von außen umfasst. Kapselung ermöglicht es, dass Objekte ihre
internen Daten verbergen (man spricht von Datenkapselung
) und nur
über definierte Schnittstellen (Methoden) angesprochen werden, fast als
ob man von einer komplexen Maschine nur ein paar Taster sieht, aber
nicht an das Innenleben gelangt. Dies erhöht die Sicherheit und
Robustheit des Code, indem verhindert wird, dass externe Funktionen oder
andere Objekte direkt auf interne Datenstrukturen zugreifen und dort
Unsinn anstellen oder spätere interne Änderungen nicht berücksichtigen.
In Python ist die Kapselung allerdings nicht streng wie in Sprachen wie
C++ und Java, sondern eher nur ein Vorschlag.
Ein weiterer Grundbegriff der OOP ist die Unterscheidung
zwischen Klassen und Objekten = Instanzen. Eine Klasse ist eine
Blaupause oder Schablone, die die Struktur (Daten, in diesem
Zusammenhang Attribute genannt) und das Verhalten (Funktionen, in diesem
Zusammenhang Methoden genannt) der Objekte definiert, die von ihr
erzeugt werden. Ein Objekt ist eine konkrete Ausprägung dieser Klasse.
Man nennt das eine Instanz
der Klasse; die Klasse wird
instanziiert
.
Beispielsweise könnte in Python eine Klasse Auto
Attribute (also Daten) wie farbe und
kennzeichen sowie Methoden (also Funktionen) wie
fahren() und bremsen() haben. Ein Auto-Objekt
ist dann eine Instanz dieser Klasse, zum Beispiel mit dem konkreten
Autokennzeichen BI XY 1234 E.
In der fortgeschritteneren Programmierung gibt man auch einer Klasse
als solcher (statt ihren einzelnen Instanzen) Attribute und
Methoden. Diese heißen dann statisch
. Beispiele dafür in einer
Klasse Auto sind die für alle Autos der Klasse gleiche und
unveränderliche Zahl der Türen oder die Zahl bisher erzeugter Instanzen
von Auto.
Vererbung ist ein weiteres wichtiges Konzept in der OOP, das es Klassen ermöglicht, Attribute und Methoden von einer anderen Klasse zu übernehmen. Dies unterstützt die Wiederverwendung von Code und die Erstellung von weit verzweigten Stammbäumen von Klassen. Wenn eine Klasse von einer anderen erbt, wird die erstere als Unterklasse, abgeleitete Klasse oder Kindklasse und die letztere als Oberklasse, Basisklasse oder Elternklasse bezeichnet.
Ein äußerst wichtiger praktischer Aspekt der Vererbung ist die
Wiederverwendung von Code durch die Nutzung von vorgefertigten
Klassenbibliotheken: Man leitet von deren Klassen eigene Klassen ab und
erbt damit eine umfangreiche Funktionalität. Dies beschleunigt die
Entwicklung erheblich und reduziert Fehler, da diese Bibliotheken in der
Regel gut getestet sind. Wir werden insbesondere die Bibliothek
PySide für grafische Bedienoberflächen als ein Beispiel
einer Klassenbibliothek kennenlernen.
Polymorphie (Vielgestaltigkeit
) ist ein
Konzept in der objektorientierten Programmierung, das es erlaubt,
Methoden mit dem gleichen Namen in verschiedenen Klassen zu verwenden.
Diese Methoden können unterschiedliche Aktionen ausführen, je nachdem,
zu welcher Klasse das Objekt gehört, das sie aufruft. Das ermöglicht es,
Objekte unterschiedlicher Klassen auf die gleiche Weise zu behandeln,
was den Code flexibler und leichter erweiterbar macht. Dies wird
insbesondere in Klassenbibliotheken ausgenutzt.
Polymorphie kann man sich wie eine Zirkusvorstellung vorstellen, bei
der jedes Tier aufgefordert wird, einen Ton zu machen. Die Aktion Ton
machen
ist für alle gleich, aber jedes Tier macht einen anderen
Ton:
Wuffmachen.
Miausagen.
Hier sagt der Name der Aktion Ton machen
nichts darüber aus,
welches spezifische Geräusch gemacht wird. Welches Geräusch herauskommt,
hängt davon ab, um welches Tier es sich handelt. In der Programmierung
ermöglicht Polymorphie genau das: Man definiert eine Methode (wie Ton
machen
) in einer Basisklasse (wie Tier
), und jede Unterklasse
(wie Hund
, Katze
oder Vogel
) kann diese Methode auf
ihre eigene Weise ausführen. Die jeweiligen Klassen überschreiben
(so der Fachbegriff) dann die Methode der Oberklasse. Auf Englisch heißt
das to override, bezeichnet
also, dass eine höhere Instanz eine Entscheidung einer niedrigeren
Instanz außer Kraft setzt.
Im Extremfall führt man für die Polymorphie eine Oberklasse ein, die
so allgemein ist, dass man die Methoden gar nicht ausformulieren kann,
eine abstrakte Klasse
. Im Beispiel oben könnte das die Klasse
Tier sein. Erst die davon erbenden Unterklassen
Hund usw. haben dann konkrete Methoden.
Es ist eine Klasse Kaffeemaschine zu schreiben, damit
dieser oben schon erwähnte Code zum Anwenden der Kaffeemaschine
funktioniert:
# Erzeugung eines Kaffeemaschinen-Objekts
meine_kaffeemaschine = Kaffeemaschine('CaffeineBlaster', 2)
# Nutzung des Kaffeemaschinen-Objekts
meine_kaffeemaschine.einschalten()
meine_kaffeemaschine.wasser_auffüllen(1) # Füge 1 Liter Wasser hinzu
meine_kaffeemaschine.kaffee_brühen() # Brühe eine Tasse Kaffee
meine_kaffeemaschine.ausschalten()Zuerst definieren wir eine Klasse Kaffeemaschine. Eine
Klasse in ist wie ein Bauplan für das Erstellen von Objekten
(Instanzen). Jedes Objekt, das von dieser Klasse erstellt wird, hat die
in der Klasse definierten Eigenschaften (Attribute = Daten) und Methoden
(Funktionen).
class Kaffeemaschine:
'''
Repräsentiert eine Kaffeemaschine mit einstellbarem Wassertank und der Fähigkeit, Kaffee zu brühen.
'''Sämtliche Bestandteile der Klasse stehen dann eingerückt (diese Einrückung nicht vergessen!) in den Zeilen darunter.
__init__Innerhalb der Klasse definieren wir die Initialisierungsfunktion
__init__ (geschrieben mit jeweils zwei Unterstrichen vorne
und hinten). Diese Funktion wird immer aufgerufen, wenn ein neues Objekt
der Klasse erstellt wird. (Technische Anmerkung: __init__
macht, was in anderen Sprachen der Konstruktor
einer Klasse
macht. In Python gibt es aber obendrein einen echten Konstruktor
__new__. Deshalb für __init__ ist die
Bezeichnung Initialisierungsfunktion
besser als
Konstruktor
.)
def __init__(self, marke: str, wassertank_kapazität: float):
'''
Initialisiert eine neue Kaffeemaschine mit einer Marke und einer Wassertankkapazität.
Args:
marke (str): Die Marke der Kaffeemaschine.
wassertank_kapazität (float): Die Kapazität des Wassertanks in Litern.
Diese Methode setzt den anfänglichen Füllstand des Wassertanks auf 0 Liter und
initialisiert die Maschine in ausgeschaltetem Zustand.
'''
self.marke: str = marke # Marke der Kaffeemaschine als String
self.wassertank_kapazität: float = wassertank_kapazität # Maximale Kapazität des Wassertanks in Litern
self.wassertank_füllstand: float = 0.0 # Aktueller Füllstand des Wassertanks in Litern, startet bei 0
self.ist_eingeschaltet: bool = False # Status der Kaffeemaschine, True wenn eingeschaltet, sonst FalseDie Variable self verweist auf die Instanz des Objekts:
Was soll mit dieser einen Kaffeemaschine passieren, die hier
beschreiben wird? Mittels self kann man in der Methoden der
Klasse auf deren Bestandteile wie Daten (Attribute) und Funktionen
(Methoden) zugreifen.
Die Typangaben wie str und float in diesem
Code sind wieder freiwillig, aber sehr empfehlenswert. Die
Initialisierungsfunktion bekommt keinen Rückgabetyp
-> irgendein_Typ, denn sie darf auch nicht mit
return irgendwas ein Ergebnis liefern, sondern darf nur am
halbfertigen Objekt (das sie in der Variablen self erhält)
weiterbasteln.
Das Setzen von self.marke = irgendwas bewirkt, dass
diese Variable (dieses Attribut) marke erstens in der
gerade behandelten Instanz angelegt wird und zweitens auf einen
bestimmten Wert gesetzt wird. Entsprechendes gilt für die weiteren drei
Attribute.
einschalten und ausschaltenDie Methode einschalten ändert den Zustand von
ist_eingeschaltet auf True und gibt eine
Nachricht aus:
def einschalten(self) -> None:
'''Schaltet die Kaffeemaschine ein.'''
self.ist_eingeschaltet = True
print(f'{self.marke} Kaffeemaschine eingeschaltet.')Die Methode ausschalten arbeitet entsprechend:
def ausschalten(self) -> None:
'''Schaltet die Kaffeemaschine aus.'''
self.ist_eingeschaltet = False
print(f'{self.marke} Kaffeemaschine ausgeschaltet.')Wie können Sie beide Methoden zu einer einzigen zusammenfassen?
wasser_auffüllenDiese Methode fügt Wasser zum Wassertank hinzu. Sie erhält die Menge des Wassers als Parameter und prüft, ob nach dem Hinzufügen der Menge der Füllstand den Tank überlaufen würde. Wenn ja, gibt sie eine Fehlermeldung aus; wenn nein, füllt sie den Tank und gibt den neuen Füllstand auf der Kommandozeile aus (aber nicht als Rückgabewert!).
def wasser_auffüllen(self, menge: float) -> None:
'''
Füllt den Wassertank um die angegebene Menge auf.
Args:
menge (float): Die Menge an Wasser in Litern, die hinzugefügt werden soll.
'''
if self.wassertank_füllstand + menge <= self.wassertank_kapazität:
self.wassertank_füllstand += menge
print(f"Wassertank aufgefüllt. Aktueller Füllstand: {self.wassertank_füllstand} Liter.")
else:
print("Fehler: Füllen abgelehnt, weil der Tank überlaufen würde!")kaffee_brühenDiese Methode versucht, eine Tasse Kaffee zu brühen. Sie prüft zunächst, ob die Maschine eingeschaltet ist und genügend Wasser im Tank vorhanden ist. Wenn beides zutrifft, brüht sie eine Tasse Kaffee, verbraucht dabei 0,25 Liter Wasser und gibt eine Erfolgsmeldung aus. Wenn nicht, gibt sie eine entsprechende Fehlermeldung aus.
def kaffee_brühen(self):
'''
Brüht eine Tasse Kaffee, wenn genügend Wasser vorhanden ist und die Maschine eingeschaltet ist.
'''
if self.ist_eingeschaltet:
if self.wassertank_füllstand >= 0.25:
self.wassertank_füllstand -= 0.25 # Verbraucht 0,25 Liter Wasser pro Tasse
print('Kaffee wird gebrüht. Eine Tasse fertig!')
else:
print('Fehler: Nicht genug Wasser.')
else:
print('Fehler: Kaffeemaschine ist ausgeschaltet.')Außerhalb der Klassendefinition, also nicht mehr eingerückt,
erstellen wir nun eine Instanz der Kaffeemaschine und
speichern sie (genauer: eine Referenz auf sie; mehr dazu später) in
einer Variable namens meine_kaffeemaschine. Hinter den
Kulissen sorgt die folgende Zeile dafür, dass unsere
Initialisierungsfunktion __init__ mit den beiden Parametern
'CaffeineBlaster' und 2 aufgerufen wird:
meine_kaffeemaschine: Kaffeemaschine = Kaffeemaschine('CaffeineBlaster', 2)(In Sprachen wie C++ und Java steht an dieser Stelle das Schüsselwort
new. Nicht so in Python.)
Ein häufiges Missverständnis: Wie man sieht, ist der Typ der
Variablen meine_kaffeemaschine die Klasse
Kaffeemaschine. Jede Instanz von
Kaffeemaschine enthält einen str, zwei
float, einen bool und kann Methoden wie
einschalten ausführen. Zusammengenommen muss das ein ganz
anderer Typ sein als etwa str oder int.
Nun benutzen wir die Methoden der Kaffeemaschine, um sie
einzuschalten, Wasser aufzufüllen und Kaffee zu brühen. Die Mühe, vorher
die Klasse Kaffeemaschine zu definieren, zahlt sich nun
aus, weil man schnell und klar schreiben kann:
meine_kaffeemaschine.einschalten()
meine_kaffeemaschine.wasser_auffüllen(1) # Füge 1 Liter Wasser hinzu
meine_kaffeemaschine.kaffee_brühen() # Brühe eine Tasse Kaffee
meine_kaffeemaschine.ausschalten()Wichtig: Erst an dieser Stelle passiert wirklich etwas. Die Klassendefinition sagt als Plan, was passieren soll, aber erst hier werden die Methoden wirklich ausgeführt.
selfIn der obigen Definition der Klasse taucht an vielen Stellen die
Variable self auf, aber nicht beim Verwenden der
Klasse, zum Beispiel nicht bei
meine_kaffeemaschine.wasser_auffüllen(1). Die Methoden
einer Instanz haben in der Definition der Klasse alle einen Parameter
mehr, als man erwarten würde: self. Dieses
self verweist auf das jeweils aktuelle Objekt, auf das die
Definition der Klasse angewendet wird.
Die Python-Laufzeitumgebung macht dazu hinter den Kulissen ungefähr
Folgendes: Wenn man
meine_kaffeemaschine.wasser_auffüllen(1) aufruft,
verwandelt sie dies in
wasser_auffüllen(meine_kaffeemaschine, 1). Auf diese Weise
landet das in der Variablen meine_kaffeemaschine angegebene
Objekt im Parameter self der Methode.
(In Sprachen wie C++ und Java gibt es statt der Variablen
self das Schüsselwort this. Es ist dort beim
Definieren der Methoden einer Klasse direkt verfügbar, ohne dass man es
als Parameter angibt. Außerdem kann es dort weggelassen werden, wenn es
keine Mehrdeutigkeiten gibt. Das self von Python ist
dagegen immer anzugeben, um in der Definition der Klasse auf
Attribute und Methoden der Instanz zuzugreifen.)