Die objektorientierten Programmierung wurde in den 1960er Jahren entwickelt und in den letzten 40 Jahren immer populärer. Sie ist aus der Programmierung nicht mehr wegzudenken. Mit Objekten lässt sich der Quellcode von komplexen Projekten übersichtlich organisieren.
Ein Objekt ist alles, was Daten zusammenfassen kann. Du hast Objekte schon kennengelernt, denn jeder String ist ein Objekt, genauso wie Listen.
Objekte als Datenspeicher
Bisher haben wir alle Programme dieses Kurs in eine einzelne Datei geschrieben. Dieses Vorgehen macht aber, gerade bei großen Projekten mit vielen Methoden und Variablen den Quellcode schnell unlesbar. Ein Objekt fasst alle Variablen, die zu ihm gehören zusammen. Sie werden dann Attribute genannt.
Hier mal das klassische Beispiel eines Objekts für eine Person, speichere sie in der Datei person.py
class Person:
# Konstruktor
def __init__(self,vorname, name):
self.vorname = vorname
self.name = name
Um ein Objekt erzeugen zu können, benötigen wir in Python eine Klasse, die das Objekt beschreibt.
Konstruktor
Der Konstruktor ist eine Spezialmethode einer Klasse, der bei der Erzeugung des Objekts aufgerufen wird. Hier wird die Objektinstanz initialisiert. In unserem Beispiel werden. Per Konvention heißt der Konstruktor in Python __init__
Die übergebenen Parameter vorname und name den Attributen zugewiesen. Der Parameter self istein besonderer Parameter von Python, mit dem das Objekt sich auf sich selbst bezieht, du musst ihn bei jeder Methode in der Klasse als erstes angeben, ansonsten
erhälst du vom Interpreter diese Fehlermeldung:
TypeError: __init__() takes 2 positional arguments but 3 were given
Dieser Fehler hat mich bei meinen Anfängen mit Python echt geärgert, daher erwähne ich das hier mal.
Die Notation des Konstruktors mit __
(doppelten Unterstrichen) ist eine Konvention von Python, mit der interne Methoden und Variablen gekennzeichnet werden. Dies verhindert den Zugriff darauf nicht, es soll nur jedem Programmierer zeigen, dass er mit solchen Attributen und Methoden vorsichtig sein muss. Diese Notation wird auch Dunder genannt, verkürzt von „Double Underscore“.
Unsere Klasse ist jetzt definiert, nun wollen wir sie auch verwenden. Erstelle die Datei useperson.py
im selben Verzeichnis wie person.py
mit dem folgen Codeblock
from person import Person
p = Person('Olli','Graf')
print(p.vorname)
die erste Zeile importiert uns die Klasse Person aus der Datei person (das .py fügt Python implizit an).
Danach wird eine Instanz von Person mit den übergebenen Werten für name
und vorname
erzeugt und der Variable p zugewiesen. Du kannst jetzt so viele Instanzen von Person
erzeugen, bis dir der Speicher ausgeht. Mehrere Instanzen solltest du dann in einem Dictionary verwalten.
Klassenmethoden
Klassen können nicht nur Attribute verwalten, sondern auch Methoden zu deren Manipulation mitbringen. Stell die vor, du bräuchtest eine Methode, die dir eine Instanz von Person
formatiert für die Ausgabe zur Verfügung stellt. Dann ergänzt du Person
wie folgt
class Person:
# Konstruktor
def __init__(self,vorname, name):
self.vorname = vorname
self.name = name
def format(self):
return self.vorname + ' ' + self.name
def __str__(self):
return self.format()
Die format()
Methode gehört jetzt zur Klasse Person
dazu und steht in jeder Instanz zur Verfügung. Daher kannst du jetzt auch useperson.py
ändern:
from person import Person
p = Person('Olli','Graf')
print(p.format())
Der Aufruf von format()
geschieht jetzt direkt am Objekt p
. Stell dir vor, du möchtest das Format der Ausgabe ändern, z.B. sollen Vorname und Nachname umgekehrt mit einem „,“ getrennt erscheinen. Dann änderst das einmal in der format() Methode der Klasse, anstatt 1000-mal in 1000 einzelnen Objektinstanzen.
Ein Tipp an Rande. Statt einer format()
Methode kannst du auch die interne Methode __str__()
verwenden und dann das Objekt mit print(p)
ausgeben.
zweite Klasse
Nur Namen und Vornamen zu speichern reicht i.d.R. für eine Person
nicht aus, es gehört üblicherweise noch eine Anschrift dazu. Wir könnten jetzt für Straße, Hausnummer, Postleitzahl und Ort weitere Attribute in Person aufnehmen, aber objektorientiert ist die Anschrift ein weiteres Objekt, für das du eine weitere Klasse in einer neuen Datei anschrift.py
deklarierst.
class Anschrift:
def __init__(self, strasse, hausnummer, plz, ort):
self.strasse = strasse
self.hausnummer = hausnummer
self.plz = plz
self.ort = ort
def __str__(self):
return self.strasse + ' ' + self.hausnummer + '\n' + self.plz + ' ' + self.ort
Neu ist hier eigentlich nur, die Trennung von Hausnummer und PLZ in __str__ \n ist ein Sonderzeichen, mit dem ein Zeilenumbruch (Newline) erzeugt wird.
Die Klasse Person modifizierst du jetzt wie folgt, um Anschrift zu benutzen.
from anschrift import Anschrift
class Person:
# Konstruktor
def __init__(self,vorname, name,strasse,hausnummer, plz,ort):
self.vorname = vorname
self.name = name
self.anschrift = Anschrift(strasse,hausnummer,plz,ort)
def format(self):
return self.vorname + ' ' + self.name
def __str__(self):
return self.format() + '\n' + str(self.anschrift)
Person
bindet jetzt ein Objekt vom Typ Anschrift
ein und legt strasse
, hausnummer
, plz
und ort
dort ab. Sowas wird in der objektorientierten Welt Assoziation genannt.
Vererbung
Objekte können ihre Attribute nicht nur durch Assoziation, sondern auch durch Vererbung zur Verfügung stellen. Nehmen wir mal folgendes Beispiel in der Datei vererbung.py
class Fahrzeug:
def __init__(self, marke, modell, farbe):
self.marke = marke
self.modell = modell
self.farbe = farbe
def __str__(self):
return f'{self.marke} {self.modell} {self.farbe})'
#Klasse Auto erbt von Fahrzeug
class Auto(Fahrzeug):
def __init__(self, marke, modell, farbe, ps):
super().__init__(marke, modell, farbe)
self.ps = ps
def __str__(self):
return f'{super().__str__()} mit {self.ps} PS'
#Klasse Fahhrad erbt von Fahrzeug
class Fahrrad(Fahrzeug):
def __init__(self, marke, modell, farbe):
super().__init__(marke, modell, farbe)
def __str__(self):
return super().__str__()
bond = Auto('Aston Martin','DB5','grau','286')
mcfly = Auto('Delorean', 'DMC-12','grau','132')
eliot = Fahrrad('Kuwahara','ET-1','weiß')
Für Vererbung benötigst du immer mind. zwei Klassen. Eine Superklasse und eine davon abgeleitete Klasse. Die Superklasse ist hier Fahrzeug
. Auto
und Fahrrad
leiten davon ab.
Die Klasse Fahrzeug
enthält Attribute für marke
, modell
und farbe
. Dies sind Attribute, die universell für alle abgeleiteten Klassen. Die beiden anderen Klassen übernehmen durch die Vererbung diese Attribute und erweitern diese ggf. durch ihren eigenen Attribute z.B. ps
bei Auto
. Deshalb sagt man auch, Auto
ist eine Spezialisierung von Fahrzeug
. Um auf Methoden und Attribute der Superklasse
zuzugreifen, wird die super()
Funktion benutzt. Dadurch werden die allgemeinen Attribute dem Konstruktor der Superklasse übergeben.
Durch die Vererbung ist die Variable bond
nicht nur vom Typ Auto
, sondern auch vom Typ Fahrzeug
.
Der Post ist jetzt sehr lang geworden. Es gibt noch viele Dinge über Objekte, Klassen und Objektorientierung zu sagen, dass verschiebe ich auf einen weiteren Beitrag zu dem Thema. Im nächsten Teil organisieren wir dann durch Module den Quellcode besser.