Objektorientierte Programmierung#
(
oop: object oriented programming)
In all den Programmen, die wir bisher geschrieben haben, haben wir unser Programm um Funktionen herum entworfen, d. h. Blöcke von Anweisungen, die Daten verarbeiten. Dies wird die prozedur-orientierte Art des Programmierens genannt. Es gibt eine andere Art, Ihr Programm zu organisieren, nämlich Daten und Funktionalität zu kombinieren und sie in etwas einzupacken, das ein Objekt genannt wird. Dies wird das objektorientierte Programmierparadigma genannt. Die meiste Zeit können Sie prozedurale Programmierung verwenden, aber beim Schreiben großer Programme oder wenn Sie ein Problem haben, das sich besser für diese Methode eignet, können Sie objektorientierte Programmiertechniken einsetzen.
Klassen und Objekte sind die beiden Hauptaspekte der objektorientierten Programmierung. Eine Klasse (class) erstellt einen neuen Typ, während Objekte Instanzen der Klasse sind. Eine Analogie ist, dass Sie Variablen des Typs int haben können, was bedeutet, dass Variablen, die ganze Zahlen speichern, Variablen sind, die Instanzen (Objekte) von int Klasse sind.
Hinweis für Programmierer statischer Sprachen
Beachten Sie, dass selbst Ganzzahlen als Objekte (der Klasse int) behandelt werden. Dies unterscheidet sich von C++ und Java (vor Version 1.5), wo Ganzzahlen primitive, native Typen sind.
Siehe help(int) für weitere Details zur Klasse.
C#- und Java-1.5-Programmierer werden dies mit dem Konzept des Boxing und Unboxing vergleichen können.
Objekte können Daten mithilfe gewöhnlicher Variablen speichern, die zum Objekt gehören. Variablen, die zu einem Objekt oder einer Klasse gehören, werden Felder genannt. Objekte können auch Funktionalität besitzen, indem Funktionen verwendet werden, die zu einer Klasse gehören. Solche Funktionen heißen Methoden der Klasse. Diese Terminologie ist wichtig, weil sie uns hilft, zwischen Funktionen und Variablen zu unterscheiden, die unabhängig sind, und solchen, die zu einer Klasse oder einem Objekt gehören. Gemeinsam können die Felder und Methoden als die Attribute dieser Klasse bezeichnet werden.
Felder gibt es in zwei Arten – sie können zu jeder einzelenen Instanz der Klasse gehören oder sie können zur ganzen Klasse (allen Instanzen gemeinsam) gehören. Sie heißen entsprechend Instanzvariablen und Klassenvariablen.
Eine Klasse wird mit dem Schlüsselwort class erzeugt. Die Felder und Methoden der Klasse werden in einem eingerückten Block aufgelistet.
Das self#
Klassenmethoden unterscheiden sich nur in einem Punkt von gewöhnlichen Funktionen – sie müssen einen zusätzlichen ersten Namen haben, der am Anfang der Parameterliste hinzugefügt werden muss, aber Sie geben beim Aufruf der Methode keinen Wert für diesen Parameter an; Python stellt ihn bereit. Diese spezielle Variable bezieht sich auf das Objekt selbst, und nach Konvention wird ihr der Name self gegeben.
Obwohl Sie diesem Parameter einen beliebigen Namen geben können, wird dringend empfohlen, den Namen self zu verwenden – jeder andere Name wird eindeutig missbilligt. Es gibt viele Vorteile bei der Verwendung eines standardisierten Namens – jeder Leser Ihres Programms erkennt ihn sofort, und sogar spezialisierte IDEs (Integrated Development Environments) können Ihnen helfen, wenn Sie self verwenden.
Hinweis für C++/Java/C#-Programmierer
Das self in Python entspricht dem this-Pointer in C++ sowie der this-Referenz in Java und C#.
Sie fragen sich sicher, wie Python den Wert für self bereitstellt und warum Sie keinen Wert dafür angeben müssen. Ein Beispiel wird dies verdeutlichen. Angenommen, Sie haben eine Klasse namens MyClass und eine Instanz dieser Klasse namens myobject. Wenn Sie eine Methode dieses Objekts wie myobject.method(arg1, arg2) aufrufen, wird dies von Python automatisch in MyClass.method(myobject, arg1, arg2) umgewandelt – das ist alles, worum es beim speziellen self geht.
Das bedeutet auch, dass wenn Sie eine Methode haben, die keine Argumente benötigt, Sie dennoch ein Argument haben müssen – das self.
Klassen#
Die einfachstmögliche Klasse wird im folgenden Beispiel gezeigt:
Beispiel oop_simplestclass_de.py
Quellcode
1class Person:
2 pass # Ein leerer Block
3
4p = Person()
5print(p)
Die Zeilennummern sind nicht Bestandteil des Quellcodes
Ausgabe
$ python3 oop_simplestclass_de.py
<__main__.Person object at 0x758768cf4c20>
Wie es funktioniert:
Wir erstellen eine neue Klasse mit der class-Anweisung und dem Namen der Klasse. Darauf folgt ein eingerückter Block von Anweisungen, die den Rumpf (body) der Klasse bilden. In diesem Fall haben wir einen leeren Block, der mit der pass-Anweisung gekennzeichnet ist.
Als Nächstes erstellen wir eine Instanz dieser Klasse, indem wir den Namen der Klasse gefolgt von einem Klammerpaar verwenden. (Wir werden später mehr über Instanziierung lernen.) Zu unserer Überprüfung bestätigen wir den Typ der Variable, indem wir sie einfach ausdrucken. Es sagt uns, dass wir eine Instanz der Klasse Person im Modul __main__ haben.
Beachten Sie, dass auch die Adresse des Computerspeichers ausgegeben wird, an der Ihr Objekt gespeichert ist. Die Adresse wird auf Ihrem Computer einen anderen Wert haben, da Python das Objekt dort speichert, wo es freien Platz findet.
Methoden#
Wir haben bereits besprochen, dass Klassen/Objekte Methoden haben können, genau wie Funktionen, außer dass wir eine zusätzliche Variable self haben. Jetzt werden wir ein Beispiel sehen:
Beispiel oop_method_de.py
Quellcode
1class Person:
2 def sag_hallo(self):
3 print("Hallo, wie geht's?")
4
5p = Person()
6p.sag_hallo()
7# Die vorigen beiden Zeilen könnte man auch schreiben als:
8# Person().sag_hallo()
Die Zeilennummern sind nicht Bestandteil des Quellcodes
Ausgabe
$ python3 oop_method_de.py
Hallo, wie geht's?
Wie es funktioniert:
Hier sehen wir self in Aktion. Beachten Sie, dass die Methode sag_hallo keine Parameter übernimmt, aber dennoch self in der Funktionsdefinition hat.
Die Methode __init__#
Es gibt viele Methodennamen, die in Python-Klassen eine besondere Bedeutung haben. Wir werden nun die Bedeutung der Methode __init__ sehen.
Die __init__-Methode wird ausgeführt, sobald ein Objekt einer Klasse instanziiert (d. h. erzeugt) wird. Die Methode ist nützlich, um jede Initialisierung vorzunehmen (d. h. Anfangswerte an Ihr Objekt zu übergeben), die Sie mit Ihrem Objekt durchführen wollen. Beachten Sie die doppelten Unterstriche sowohl am Anfang als auch am Ende des Namens.
Beispiel oop_init_de.py
Quellcode
1class Person:
2 def __init__(self, name):
3 self.name = name
4
5 def sag_hallo(self):
6 print('Hallo, ich heiße', self.name)
7
8p = Person('Swaroop')
9p.sag_hallo()
10# Man könnte auch beide vorigen Zeilen in einer Zeile schreiben:
11# Person('Swaroop').sag_hallo()
Die Zeilennummern sind nicht Bestandteil des Quellcodes
Ausgabe
$ python3 oop_init_de.py
Hallo, ich heiße Swaroop
Wie es funktioniert:
Hier definieren wir die __init__-Methode so, dass sie einen Parameter namens name übernimmt (zusammen mit dem üblichen self). Wir erstellen einfach ein neues Feld, ebenfalls name genannt. Beachten Sie, dass dies zwei unterschiedliche Variablen sind, auch wenn sie beide name heißen. Es gibt kein Problem, weil die Punktnotation self.name bedeutet, dass es etwas namens name gibt, das Teil des Objekts self ist, und das andere name ist eine lokale Variable. Da wir explizit angeben, auf welchen Namen wir uns beziehen, gibt es keine Verwechslung.
Beim Erzeugen der neuen Instanz p der Klasse Person tun wir dies, indem wir den Klassennamen gefolgt von den Argumenten in Klammern verwenden: p = Person('Swaroop').
Wir rufen die __init__-Methode nicht explizit auf. Das ist die besondere Bedeutung dieser Methode.
Jetzt können wir das Feld self.name in unseren Methoden verwenden, was in der Methode sag_hallo demonstriert wird.
Klassen- und Objektvariablen#
Wir haben bereits den funktionalen Teil von Klassen und Objekten besprochen (d. h. Methoden), nun wollen wir etwas über den Datenteil lernen. Der Datenteil, d. h. Felder, sind nichts anderes als gewöhnliche Variablen, die an die Namensräume der Klassen und Objekte gebunden sind. Das bedeutet, dass diese Namen nur im Kontext dieser Klassen und Objekte gültig sind. Daher nennt man sie Namensräume.
Es gibt zwei Arten von Feldern – Klassenvariablen und Objektvariablen, je nachdem, ob die Klasse oder das Objekt die Variablen besitzt.
Klassenvariablen gehören allen gemeinsam – sie können von allen Instanzen dieser Klasse verwendet werden. Es gibt nur eine Kopie der Klassenvariable, und wenn ein Objekt eine Klassenvariable ändert, wird diese Änderung von allen anderen Instanzen gesehen.
Objektvariablen gehören zu jedem einzelnen Objekt/Instanz der Klasse. In diesem Fall hat jedes Objekt seine eigene Kopie des Feldes, d. h. sie werden nicht geteilt und stehen nicht in Beziehung zu einem Feld gleichen Namens in einer anderen Instanz. Ein Beispiel macht dies leichter verständlich:
Beispiel oop_objvar_de.py
Quellcode
1class Robot:
2 """Repräsentiert einen Roboter mit einem Namen"""
3
4 # Eine class variable, sie zählt die Anzahl der Roboter
5 population = 0
6
7 def __init__(self, name):
8 """Intitialisiere Daten"""
9 self.name = name
10 print("(Initializing {})".format(self.name))
11
12 # Der "frisch gebackene" Robotor erhöht die Roboter-Population
13 Robot.population += 1
14
15 def stirb(self):
16 """Ich sterbe."""
17 print("{} ist zerstört!".format(self.name))
18
19 Robot.population -= 1
20
21 if Robot.population == 0:
22 print("{} war der letzte seiner Art.".format(self.name))
23 else:
24 print("Es sind noch {} Roboter übrig.".format(
25 Robot.population))
26
27 def sag_hallo(self):
28 """
29 Roboter können grüßen.
30 Wirklich!
31 """
32 print("Grüße, ich werde {} genannt.".format(self.name))
33
34 @classmethod
35 def wieviele_gibt_es(cls):
36 """druckt die Anzahl aller Roboter aus."""
37 print("Derzeit gibt es {} Roboter.".format(cls.population))
38
39
40droid1 = Robot("R2-D2")
41droid1.sag_hallo()
42Robot.wieviele_gibt_es()
43
44droid2 = Robot("C-3PO")
45droid2.sag_hallo()
46Robot.wieviele_gibt_es()
47
48print("\nHier könnten die Roboter arbeiten\n")
49
50print("Arbiet ist fertig, Roboter werden zerstört....")
51droid1.stirb()
52droid2.stirb()
53
54Robot.wieviele_gibt_es()
Die Zeilennummern sind nicht Bestandteil des Quellcodes
Ausgabe
$ python3 oop_objvar_de.py
(Initializing R2-D2)
Grüße, ich werde R2-D2 genannt.
Derzeit gibt es 1 Roboter.
(Initializing C-3PO)
Grüße, ich werde C-3PO genannt.
Derzeit gibt es 2 Roboter.
Hier könnten die Roboter arbeiten
Arbiet ist fertig, Roboter werden zerstört....
R2-D2 ist zerstört!
Es sind noch 1 Roboter übrig.
C-3PO ist zerstört!
C-3PO war der letzte seiner Art.
Derzeit gibt es 0 Roboter.
Wie es funktioniert:
Dies ist ein langes Beispiel, hilft aber dabei, die Natur von Klassen- und Objektvariablen zu verdeutlichen. Hier gehört population zur Klasse Robot und ist daher eine Klassenvariable. Die Variable name gehört zum Objekt (sie wird mit self zugewiesen) und ist daher eine Objektvariable.
Daher verweisen wir auf die Klassenvariable population mit Robot.population und nicht mit self.population. Wir verweisen auf die Objektvariable name mit der Notation self.name in den Methoden dieses Objekts. Merken Sie sich diesen einfachen Unterschied zwischen Klassen- und Objektvariablen. Beachten Sie auch, dass eine Objektvariable mit demselben Namen wie eine Klassenvariable die Klassenvariable verdeckt!
Anstelle von Robot.population könnten wir auch self.__class__.population verwenden, weil jedes Objekt über das Attribut self.__class__ auf seine Klasse verweist.
Die Methode wieviele_gibt_es gehört tatsächlich zur Klasse und nicht zum Objekt. Das bedeutet, wir können sie entweder als classmethod oder als staticmethod definieren, abhängig davon, ob wir wissen müssen, zu welcher Klasse wir gehören. Da wir auf eine Klassenvariable verweisen, verwenden wirclassmethod.
Wir haben die Methode wieviele_gibt_es als Klassenmethode mit einem Dekorator markiert.
Dekoratoren kann man sich als eine Abkürzung zur Anwendung einer Wrapper-Funktion vorstellen (d. h. eine Funktion, die eine andere Funktion “einwickelt”, sodass sie etwas vorher oder nachher tun kann). Das Anwenden des Dekorators @classmethod ist gleichbedeutend mit dem Aufruf:
how_many = classmethod(how_many)
Beachten Sie, dass die Methode __init__ verwendet wird, um die Robot-Instanz mit einem Namen zu initialisieren. In dieser Methode erhöhen wir den population-Zähler um 1, da ein weiterer Roboter hinzugefügt wurde. Beachten Sie auch, dass die Werte von self.name spezifisch für jedes Objekt sind, was die Natur der Objektvariablen zeigt.
Denken Sie daran, dass Sie auf die Variablen und Methoden desselben Objekts ausschließlich mit self verweisen dürfen. Dies wird als Attributreferenz bezeichnet.
In diesem Programm sehen wir auch die Verwendung von Docstrings sowohl für Klassen als auch für Methoden. Wir können den Docstring der Klasse zur Laufzeit mit Robot.__doc__ und den Docstring der Methode mit Robot.sag_hallo.__doc__ abrufen.
In der Methode die verringern wir einfach den Zähler Robot.population um 1.
Alle Klassenmitglieder sind öffentlich. Eine Ausnahme: Wenn Sie Datenmember mit Namen verwenden, die den Doppelunterstrich-Präfix haben, wie __privatevar, verwendet Python Name-Mangling, um daraus effektiv eine private Variable zu machen.
Daher ist die Konvention, dass jede Variable, die nur innerhalb der Klasse oder des Objekts verwendet werden soll, mit einem Unterstrich beginnen sollte, und alle anderen Namen sind öffentlich und können von anderen Klassen/Objekten verwendet werden. Denken Sie daran, dass dies nur eine Konvention ist und nicht von Python erzwungen wird (außer beim Doppelunterstrich-Präfix).
Hinweis für C++/Java/C#-Programmierer
Alle Klassenmitglieder (einschließlich der Datenmember) sind öffentlich (public) und alle Methoden sind in Python virtuell (virtual).
Vererbung#
Einer der größten Vorteile der objektorientierten Programmierung ist die Wiederverwendung von Code, und eine der Möglichkeiten, wie dies erreicht wird, ist der Mechanismus der Vererbung. Vererbung kann man sich am besten als eine Typ- und Subtyp-Beziehung zwischen Klassen vorstellen.
Angenommen, Sie möchten ein Programm schreiben, das die Lehrer und Schüler in einem College verwaltet. Sie haben einige gemeinsame Merkmale wie Name, Alter und Adresse. Sie haben auch spezifische Merkmale wie Gehalt, Kurse und Urlaubstage für Lehrer sowie Noten und Gebühren für Schüler.
Sie könnten zwei unabhängige Klassen für jeden Typ erstellen und diese verarbeiten, aber das Hinzufügen eines neuen gemeinsamen Merkmals würde bedeuten, dass Sie es zu beiden unabhängigen Klassen hinzufügen müssen. Dies wird schnell unübersichtlich.
Eine bessere Methode wäre, eine gemeinsame Klasse namens SchoolMember zu erstellen und dann die Klassen Teacher und Student von dieser Klasse erben zu lassen, d. h. sie werden Untertypen dieses Typs (der Klasse), und dann können wir spezifische Merkmale zu diesen Untertypen hinzufügen.
Dieser Ansatz hat viele Vorteile. Wenn wir Funktionalität in SchoolMember hinzufügen oder ändern, wirkt sich dies automatisch auch auf die Untertypen aus. Beispielsweise können Sie ein neues ID-Karten-Feld sowohl für Lehrer als auch für Schüler hinzufügen, indem Sie es einfach zur Klasse SchoolMember hinzufügen. Änderungen in den Untertypen beeinflussen jedoch nicht die anderen Untertypen. Ein weiterer Vorteil ist, dass Sie ein Lehrer- oder Schülerobjekt als ein SchoolMember-Objekt betrachten können, was in manchen Situationen nützlich sein kann, z. B. beim Zählen der Anzahl von Schulmitgliedern. Dies wird Polymorphismus genannt, bei dem ein Untertyp in jeder Situation ersetzt werden kann, in der ein Obertyp erwartet wird, d. h. das Objekt kann als Instanz der Elternklasse behandelt werden.
Beachten Sie auch, dass wir den Code der Elternklasse wiederverwenden und ihn nicht in verschiedenen Klassen wiederholen müssen, wie wir es hätten tun müssen, wenn wir unabhängige Klassen verwendet hätten.
Die Klasse SchoolMember wird in dieser Situation als Basisklasse oder Superklasse bezeichnet. Die Klassen Teacher und Student werden die abgeleiteten Klassen (derived class) oder Subklassen genannt.
Wir sehen dieses Beispiel nun als Programm an:
Beispiel oop_subclass_de.py
Quellcode
1class Mensch:
2 '''Repräsentiert sowohl Schüler als auch Lehrer'''
3 def __init__(self, name, alter):
4 self.name = name
5 self.alter = alter
6 print('(Initialisiere Mensch: {})'.format(self.name))
7
8 def info(self):
9 '''Druckt Zusammenfassung (ohne Zeilenende!)'''
10 print('Name:"{}" Alter:"{}"'.format(self.name, self.alter), end=" ")
11
12
13class Lehrer(Mensch):
14 '''Repräsentiert einen Lehrer'''
15 def __init__(self, name, alter, gehalt):
16 Mensch.__init__(self, name, alter)
17 self.gehalt = gehalt
18 print('(Initialisiere Lehrer: {})'.format(self.name))
19
20 def info(self):
21 Mensch.info(self)
22 print('Gehalt: {}'.format(self.gehalt))
23
24
25class Schüler(Mensch):
26 '''Repräsentiert einen Schüler'''
27 def __init__(self, name, alter, noten):
28 Mensch.__init__(self, name, alter)
29 self.noten = noten
30 print('(Initialisiere Schüler: {})'.format(self.name))
31
32 def info(self):
33 Mensch.info(self)
34 print('Noten: {}'.format(self.noten))
35
36l = Lehrer('Mrs. Shrividya', 40, 30000)
37s = Schüler('Swaroop', 25, 75)
38
39# Drucke eine Leerzeile
40print()
41
42personen = [l, s]
43for eine_person in personen:
44 # funktioniert sowohl für Lehrer wie auch für Schüler
45 eine_person.info()
Die Zeilennummern sind nicht Bestandteil des Quellcodes
Ausgabe
$ python3 oop_subclass_de.py
(Initialisiere Mensch: Mrs. Shrividya)
(Initialisiere Lehrer: Mrs. Shrividya)
(Initialisiere Mensch: Swaroop)
(Initialisiere Schüler: Swaroop)
Name:"Mrs. Shrividya" Alter:"40" Gehalt: 30000
Name:"Swaroop" Alter:"25" Noten: 75
Wie es funktioniert:
Um Vererbung zu verwenden, geben wir die Namen der Basisklassen in einem Tupel nach dem Klassennamen in der Klassendefinition an (zum Beispiel class Lehrer(Mensch)). Als Nächstes sehen wir, dass die Methode __init__ der Basisklasse explizit mit der Variablen self aufgerufen wird, sodass wir den Basisklassenanteil einer Instanz in der Unterklasse initialisieren können. Dies ist sehr wichtig zu merken – da wir eine __init__-Methode in den Unterklassen Lehrer und Schüler definieren, ruft Python den Konstruktor der Basisklasse Mensch nicht automatisch auf; Sie müssen ihn ausdrücklich selbst aufrufen.
Wenn wir dagegen keine __init__-Methode in einer Unterklasse definiert haben, ruft Python automatisch den Konstruktor der Basisklasse auf.
Während wir Instanzen von Lehrer oder Schüler wie eine Instanz von Mensch behandeln könnten und auf die info-Methode von Mensch zugreifen könnten, indem wir einfach Lehrer.info oder Schüler.info schreiben, definieren wir stattdessen eine weitere info-Methode in jeder Unterklasse (wobei wir die Methode tell der Klasse Mensch für einen Teil davon verwenden), um sie für diese Unterklasse anzupassen. Aufgrund dessen verwendet Python bei Lehrer.info die info-Methode der Unterklasse statt der Oberklasse. Wenn es jedoch keine info-Methode in der Unterklasse gäbe, würde Python die info-Methode der Oberklasse verwenden. Python beginnt immer damit, Methoden im tatsächlichen Typ der Unterklasse zu suchen, und wenn es dort nichts findet, sucht es in den Basisklassen der Unterklasse, eine nach der anderen, in der Reihenfolge, in der sie im Tupel der Klassendefinition angegeben sind (hier haben wir nur eine Basisklasse, aber es könnten mehrere vorhanden sein).
Ein Hinweis zur Terminologie – wenn mehr als eine Klasse im Vererbungs-Tupel aufgelistet ist, nennt man dies Mehrfachvererbung (multiple inheritance).
Der Parameter end wird in der print-Funktion in der info()-Methode der Oberklasse verwendet, um eine Zeile auszugeben und die nächste Ausgabe in derselben Zeile fortzusetzen. Dies ist ein Trick, um zu verhindern, dass print ein \n (Newline-Symbol) am Ende der Ausgabe druckt.
Zusammenfassung#
Wir haben nun die verschiedenen Aspekte von Klassen und Objekten sowie die damit verbundenen Terminologien untersucht. Wir haben auch die Vorteile und Fallstricke der objektorientierten Programmierung betrachtet. Python ist stark objektorientiert, und das sorgfältige Verständnis dieser Konzepte wird Ihnen langfristig sehr helfen.
Als Nächstes werden wir lernen, wie man mit Eingabe/Ausgabe umgeht und wie man in Python auf Dateien zugreift.