Einführung
In all unseren bisherigen Programmen haben wir unser Programm um Funktionen oder Anweisungsblöcke herum entwickelt, in denen Daten manipuliert werden. Dies wird die prozedurorientierte Programmierweise genannt. Es gibt eine andere Weise, sein Programm zu organisieren, die darin besteht, Daten und Funktionalität zu kombinieren und sie in etwas zusammenzupacken, das man ein Objekt nennt. Dies wird das objektorientierte Paradigma der Programmierung genannt. Meistens kann man mit prozedurorientierter Programmierung auskommen, aber wenn man große Programme schreiben will oder eine Lösung haben möchte, die dafür besser geeignet ist, dann sollte man objektorientierte Programmiertechniken verwenden.
Klassen und Objekte sind die beiden Hauptaspekte der objektorientierten Programmierung.
Eine Klasse erzeugt einen neuen Datentyp,
wobei Objekte die Instanzen der Klasse sind.
Man kann dies damit vergleichen, dass man Variablen vom Datentyp int
haben kann,
was dann mit anderen Worten bedeutet, dass Variablen, die Ganzzahlen speichern, Variablen sind, die Instanzen (Objekte)
der Klasse int
sind.
Anmerkung für C/C++/Java/C#-Programmierer
Beachten Sie, dass in Python sogar Ganzzahlen als Objekte (der Klasse int
)
behandelt werden. Dies ist anders als C++ und Java (vor Version 1.5), wo Ganzzahlen primitive eingebaute
Datentypen sind. Siehe help(int)
für weitere Einzelheiten zu dieser Klasse.
C#- und Java-1.5-Programmierer wird dieses Konzept bekannt vorkommen, weil es dem Boxing/Unboxing-Mechanismus ähnlich ist.
Objekte können Daten in gewöhnlichen Variablen speichern, die zu dem Objekt gehören. Variablen, die zu einem Objekt oder einer Klasse gehören, werden Felder genannt. Objekte können auch Funktionalität aufweisen, die durch Funktionen ausgelöst werden, die zu der Klasse gehören. Solche Funktionen werden Methoden der Klasse genannt. Die Sprechweise ist wichtig, weil sie uns hilft, zwischen Funktionen und Variablen zu unterscheiden, die separat für sich stehen, und solchen, die zu einer Klasse oder einem Objekt gehören. Insgesamt werden die Felder und Methoden als die Attribute der Klasse bezeichnet.
Von Feldern gibt es zwei Typen - sie können entweder zu jeder Instanz (jedem Objekt) der Klasse gehören, oder sie können zur Klasse selbst gehören. Dementsprechend werden sie im ersten Fall Instanzvariablen und im zweiten Fall Klassenvariablen genannt.
Eine Klasse wird mit dem Schlüsselwort class
erzeugt. Die Felder und Methoden
der Klasse werden in einem eingerückten Block aufgelistet.
Klassenmethoden haben nur eine Besonderheit gegenüber gewöhnlichen Funktionen - sie müssen
einen zusätzlichen ersten Namen haben, der am Beginn der Parameterliste hinzugefügt wird,
dabei übergibt man diesem Parameter jedoch nicht
einen Wert, wenn man die Methode aufruft, sondern Python sorgt dafür. Diese spezielle Variable
repräsentiert das Objekt selbst, und es hat sich eingebürgert, ihr den Namen self
zu geben.
Man könnte diesem Parameter zwar irgendeinen Namen geben, aber es wird dennoch ausdrücklich
empfohlen, den Namen self
zu benutzen - jeder andere Name ist
an dieser Stelle eindeutig verpönt. Es hat viele Vorzüge, einen Standardnamen zu benutzen - jeder Leser
Ihres Programms wird ihn sofort erkennen, und spezielle IDEs (Integrierte
Entwicklungsumgebungen) können einen unterstützen, wenn man die Bezeichnung self
verwendet.
Anmerkung für C++/Java/C#-Programmierer
Das self
in Python entspricht dem self
-Zeiger in C++
und dem this
in Java und C#.
Sie fragen sich wahrscheinlich, auf welche Weise Python den Wert für self
übergibt und warum Sie dafür keinen Wert zu übergeben brauchen. Ein Beispiel wird dies klar machen.
Angenommen, Sie haben eine Klasse, die MeineKlasse
heißt und eine
Instanz dieser Klasse, die MeinObjekt
heißt. Wenn Sie eine Methode dieses
Objekts als MeinObjekt.methode(param1, param2)
aufrufen, dann wird dies von
Python automatisch in MeineKlasse.methode(MeinObjekt, param1, param2)
umgewandelt - und genau dafür ist dieses besondere self
da.
Das bedeutet auch, dass man selbst bei einer Methode, die keine Parameter entgegennimmt,
dennoch diese Methode mit einem self
-Parameter definieren muss.
Klassen
Die einfachste mögliche Klasse zeigt das folgende Beispiel.Beispiel 11.1. Erzeugen einer Klasse
#!/usr/bin/python class Person: pass # Ein leerer Block p = Person() print(p)
So funktioniert es
Wir erzeugen eine neue Klasse, indem wir die Anweisungclass
benutzen,
gefolgt von dem Namen der Klasse. Darauf folgt ein eingerückter Block von Anweisungen,
die den Rumpf der Klasse bilden. In diesem Fall haben wir einen leeren Block, was durch
die Anweisung pass
angezeigt wird.
Als Nächstes erzeugen wir ein Objekt (eine Instanz) dieser Klasse, indem wir den Namen
der Klasse benutzen, gefolgt von einem Klammerpaar. (Wir werden im nächsten Abschnitt
mehr über diese so genannte Instanziierung lernen).
Zur Sicherheit überprüfen wir den Typ der Variable, indem wir sie einfach mit print
ausgeben. Dies zeigt uns, dass es sich um eine Instanz der Klasse Person
im Modul __main__
handelt.
Beachten Sie, dass die Adresse, an der Ihr Objekt im Hauptspeicher Ihres Computers gespeichert ist, ebenfalls ausgegeben wird. Diese Adresse wird auf Ihrem Computer einen anderen Wert haben, weil Python die Objekte überall speichern kann, wo es Platz dafür gibt.
Objektmethoden
Wir haben bereits besprochen, dass Klassen/Objekte Methoden haben können, die bis auf den zusätzlichen Übergabeparameterself
gewöhnliche Funktionen sind.
Wir werden hierfür nur ein Beispiel sehen.
Benutzung von Objektmethoden
#!/usr/bin/python class Person: def say_hi(self): print('Hello, how are you?') p = Person() p.say_hi() # Zugriff auf die Objektmethode auch direkt über die Klasse möglich: # Person().say_hi() print() Person().say_hi()
So funktioniert es
Hier sehen wir, wie die Sache mit dem self
funktioniert.
Beachten Sie, dass die Methode sagHallo
keinen Parameter
entgegennimmt, aber dennoch das self
in der Funktionsdefinition hat.
Die __init__-Methode
Es gibt viele Methodennamen, die in Pythonklassen eine besondere Bedeutung haben.
Wir werden nun die Bedeutung der Methode __init__
sehen.
Die Methode __init__
wird aufgerufen, sobald ein Objekt einer Klasse
instanziiert wird. Die Methode kann dafür benutzt werden, ihr Objekt auf irgendeine Weise zu
initialisieren. Beachten Sie die doppelten Unterstriche sowohl am Anfang als
auch am Ende des Namens.
Beispiel 11.3. Benutzung der __init__-Method
#!/usr/bin/python class Person: def __init__(self, name): self.name = name def say_hi(self): print('Hello, my name is', self.name) p = Person('Swaroop') p.say_hi() # The previous 2 lines can also be written as # Person('Swaroop').say_hi()
So funktioniert es
Hier definieren wir die Methode __init__
so,
dass sie einen Parameter name
entgegennimmt
(zusammen mit dem üblichen self
).
Wir erzeugen hier einfach ein neues Feld, das ebenfalls name
heißt.
Beachten Sie, dass dies zwei unterschiedliche Variablen sind, obwohl sie den gleichen Namen haben.
Die Schreibweise mit dem Punkt ermöglicht es uns, zwischen den beiden zu unterscheiden.
Beachten Sie vor allem, dass wir die Methode __init__
nicht explizit aufrufen, sondern die Argumente in Klammern nach dem Klassennamen übergeben,
wenn wir eine neue Instanz der Klasse erzeugen. Das ist die besondere Bedeutung dieser Methode.
Nun können wir das Feld self.name
in unseren Methoden benutzen,
wie es anhand der Methode sagHallo
demonstriert wird.
Anmerkung für C++/Java/C#-Programmierer
Die Methode __init__
entspricht einem
Konstruktor in C++, C# oder Java.
Wir haben bereits den Teil der Klassen und Objekte besprochen, der für ihre Funktionalität sorgt; nun werden wir den Teil betrachten, der die Daten enthält. Eigentlich handelt es sich um nichts anderes als gewöhnliche Variablen, die an die Namensräume der Klassen und Objekte gebunden sind, d.h. die Namen sind nur innerhalb des Kontextes der Klassen und Objekte gültig.
Es gibt zwei Arten von Feldern - Klassenvariablen und Objektvariablen, die danach klassifiziert werden, ob die Klasse oder das Objekt die jeweiligen Variablen besitzt.
Klassenvariablen werden gemeinsam benutzt, in dem Sinne, dass auf sie von allen Objekten (Instanzen) der Klasse zugegriffen wird. Es gibt nur eine Kopie einer Klassenvariable, und wenn irgendein Objekt eine Änderung an einer Klassenvariable vornimmt, dann spiegelt sich diese Änderung sofort auch in allen anderen Instanzen der Klasse wieder.
Objektvariablen gehören den einzelnen Objekten (Instanzen) der Klasse individuell. In diesem Fall hat jedes Objekt seine eigene Kopie des Feldes, d.h. sie werden nicht gemeinsam benutzt und sind auf keine Weise mit dem Feld des gleichen Namens in einer anderen Instanz der selben Klasse verknüpft. An einem Beispiel werden wir das leicht verstehen.
Benutzung von Klassen- und Objektvariablen
Beispiel 11.4. Benutzung von Klassen- und Objektvariablen
class Robot: """Represents a robot, with a name.""" # A class variable, counting the number of robots population = 0 def __init__(self, name): """Initializes the data.""" self.name = name print("(Initializing {})".format(self.name)) # When this person is created, the robot # adds to the population Robot.population += 1 def die(self): """I am dying.""" print("{} is being destroyed!".format(self.name)) Robot.population -= 1 if Robot.population == 0: print("{} was the last one.".format(self.name)) else: print("There are still {:d} robots working.".format( Robot.population)) def say_hi(self): """Greeting by the robot. Yeah, they can do that.""" print("Greetings, my masters call me {}.".format(self.name)) @classmethod def how_many(cls): """Prints the current population.""" print("We have {:d} robots.".format(cls.population)) droid1 = Robot("R2-D2") droid1.say_hi() Robot.how_many() droid2 = Robot("C-3PO") droid2.say_hi() Robot.how_many() print("\nRobots can do some work here.\n") print("Robots have finished their work. So let's destroy them.") droid1.die() droid2.die() Robot.how_many()
So funktioniert es
Dies ist ein langes Beispiel, aber es hilft uns, das Wesen von Klassen- und
Objektvariablen zu demonstrieren. Die Variable bevoelkerung
gehört hier zur Klasse Person
und ist daher
eine Klassenvariable. Die Variable name
gehört
dagegen zum jeweiligen Objekt (das durch self
zugewiesen wird)
und ist daher eine Objektvariable.
Wir nehmen daher auf die Klassenvariable bevoelkerung
Bezug, indem wir schreiben Person.bevoelkerung
und nicht
self.bevoelkerung
. Beachten Sie, dass eine Objektvariable
mit dem gleichen Namen wie eine Klassenvariable diese Klassenvariable versteckt!
Wir nehmen auf die Objektvariable name
Bezug,
indem wir die Schreibweise self.name
in den Methoden
dieses Objekts benutzen. Merken Sie sich diesen einfachen Unterschied zwischen
Klassen- und Objektvariablen.
Beachten Sie, dass die __init__
-Methode benutzt wird,
um die Instanz von Person
mit einem Namen zu initialisieren.
In dieser Methode erhöhen wir den Zähler bevoelkerung
um 1,
weil wir eine weitere Person hinzugefügt haben. Beachten Sie auch, dass der Wert von
self.name
vom jeweiligen Objekt abhängt, was das Wesen von
Objektvariablen ausmacht.
Denken Sie daran, dass Sie auf die Variablen und Methoden des gleichen Objekts
nur mit Hilfe der Variablen
self
Bezug nehmen können. Dies wird als
Attribut-Referenzierung bezeichnet.
Wir sehen in diesem Programm auch die Benutzung von
Dokumentations-Strings sowohl
für Klassen als auch für Methoden. Wir können auf den Dokumentations-String
der Klasse zur Laufzeit mit Person.__doc__
zugreifen,
und mit Person.sagHallo.__doc__
auf den
Dokumentations-String der Methode.
Ganz ähnlich zur Methode __init__
gibt es eine andere
spezielle Methode __del__
, die aufgerufen wird, wenn
ein Objekt aufhört zu leben, d.h. wenn es nicht mehr gebraucht wird und der von ihm belegte Speicher
an das System zur Wiederverwendung zurückgegeben wird. In dieser Methode erniedrigen wir einfach
den Zähler Person.bevoelkerung
um 1.
Die __del__
-Methode wird automatisch aufgerufen,
wenn das Objekt nicht mehr gebraucht wird, und es gibt keine Garantie dafür,
wann dies der Fall ist. Wenn Sie dies explizit tun wollen,
müssen Sie einfach die del
-Anweisung benutzen, die wir bereits
aus früheren Beispielen kennen.
Anmerkung für C++/Java/C#-Programmierer
In Python sind alle Mitglieder, d.h. alle Attribute einschließlich der Felder public, und alle Methoden sind virtual.
Eine Ausnahme: Wenn Sie Felder verwenden, deren Namen mit
einem doppelten Unterstrich beginnt,
so wie __privatvar
, dann modifiziert Python
den Namen so, dass daraus praktisch eine private Variable wird.
Daraus hat sich die Konvention ergeben, dass Variablen, die nur innerhalb der Klasse oder des Objekts benutzt werden, mit einem Unterstrich beginnen sollten, während alle anderen Namen öffentlich sind und von anderen Klassen/Objekten benutzt werden können. Denken Sie daran, dass dies nur eine Konvention ist, und nicht von Python erzwungen wird (außer bei Variablen, deren Name mit einem doppelten Unterstrich beginnt).
Beachten Sie auch, dass die __del__
-Methode
dem Konzept eines Destruktors entspricht.
Vererbung
Eine der Hauptvorzüge objektorientierter Programmierung ist die Möglichkeit der Wiederverwendung von Programmcode, und eine Weise, mit der dies erreicht wird, ist durch den Mechanismus der Vererbung. Vererbung kann man sich am besten als eine mit Klassen realisierte Beziehung zwischen Datentypen und Unterdatentypen vorstellen.
Angenommen, Sie wollen ein Programm schreiben, das die Dozenten und Studenten an einer Hochschule verwaltet. Diese beiden Personengruppen haben einige gemeinsame Merkmale wie Name, Alter und Adresse. Sie haben auch einige spezielle Merkmale wie Gehalt, Vorlesungen und Beurlaubungen für Dozenten und Prüfungsnoten und Studiengebühren für Studenten.
Sie können zwei unabhängige Klassen für die beiden Typen und Prozesse anlegen, aber dies würde bedeuten, dass Sie, wenn Sie ein neues gemeinsames Merkmal hinzufügen wollen, es bei jeder der beiden voneinander unabhängigen Klassen tun hinzufügen müssen. Das wird sehr schnell unhandlich.
Eine bessere Lösung besteht darin, eine gemeinsame Klasse namens SchulMitglied
anzulegen, und dann die Klasssen für Dozenten und Studenten von dieser Klasse erben
zu lassen, d.h. sie werden zu Unterdatentypen dieses Datentyps (dieser Klasse) gemacht, und wir können diesen
Unterdatentypen dann weitere besondere Merkmale hinzufügen.
Dieser Ansatz hat viele Vorteile. Wenn wir der Klasse SchulMitglied
Funktionalität hinzufügen oder diese ändern, dann spiegelt sich dies automatisch in den Unterdatentypen
wieder. Zum Beispiel können Sie ein neues Feld für die Nummer einer Identitätskarte sowohl für Dozenten
als auch für Studenten hinzufügen, indem Sie dies einfach in der Klasse SchulMitglied
ergänzen. Änderungen an den Unterdatentypen haben jedoch keine Auswirkung auf andere Unterdatentypen.
Ein weiterer Vorteil besteht darin, dass man auf ein Objekt der Klasse Dozent oder Student auch als ein
Objekt der Klasse SchulMitglied
Bezug nehmen kann, was in einigen Situationen
nützlich sein könnte, z. B. wenn man die Anzahl der Mitglieder der Hochschule zählen möchte.
Diese Möglichkeit, einen Unterdatentyp in jeder Situation, wo ein Elterndatentyp erwartet wird,
ersetzen zu können, d.h. ein Objekt als eine Instanz der Elternklasse behandeln zu können,
wird Polymorphismus genannt.
Beachten Sie auch, dass wir den Programmcode der Elternklasse wiederverwenden, dass wir ihn also nicht in den verschiedenen Klassen wiederholen müssen, wie wir es hätten tun müssen, wenn wir unabhängige Klassen benutzt hätten.
Die Klasse SchulMitglied
wird in dieser Konstellation auch
als die Basisklasse oder die Superklasse
bezeichnet. Die Klassen Dozent
und Student
werden die abgeleiteten Klassen oder Subklassen
genannt.
Hier ist nun das Beispiel als Programm.
Beispiel 11.5. Verwendung von Vererbung
class SchoolMember(): '''Represents any school member.''' def __init__(self, name, age): self.name = name self.age = age print('(Initialized SchoolMember: {})'.format(self.name)) def tell(self): '''Tell my details.''' print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ") class Teacher(SchoolMember): '''Represents a teacher.''' def __init__(self, name, age, salary): SchoolMember.__init__(self, name, age) self.salary = salary print('(Initialized Teacher: {})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('Salary: "{:d}"'.format(self.salary)) class Student(SchoolMember): '''Represents a student.''' def __init__(self, name, age, marks): SchoolMember.__init__(self, name, age) self.marks = marks print('(Initialized Student: {})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('Marks: "{:d}"'.format(self.marks)) t = Teacher('Mrs. Shrividya', 40, 30000) s = Student('Swaroop', 25, 75) # prints a blank line print() members = [t, s] for member in members: # Works for both Teachers and Students member.tell()
So funktioniert es
Um Vererbung zu benutzen, geben wir die Namen der Basisklassen
in einem Tupel an, das in der Klassendefinition dem Namen der Klasse folgt.
Danach ist zu beachten, dass die __init__
-Methode
der Basisklasse unter Benutzung der self
-Variable
explizit aufgerufen wird, damit der Anteil des Objekts, der von der Basisklasse
bereitgestellt wird, initialisiert werden kann. Es ist sehr wichtig, sich dies zu merken -
Python ruft nicht automatisch den Konstruktor der Basisklasse auf; Sie müssen
ihn selber explizit aufrufen.
Wir sehen hier auch, dass wir Methoden der Basisklasse aufrufen können,
indem wir den Namen der Basisklasse mit einem Punkt voranstellen, und
dann die Variable self
zusammen mit den anderen
Parametern der Methode übergeben.
Beachten Sie, dass wir Instanzen von Dozent
und Student
einfach wie Instanzen von
SchulMitglied
behandeln, wenn wir die
auskunft
-Methode der Klasse
SchulMitglied
benutzen.
Beachten Sie auch, dass die auskunft
-Methode
der Subklassen aufgerufen wird, und nicht die auskunft
-Methode
der Superklasse SchulMitglied
. Man kann dies so verstehen,
dass Python immer zuerst versucht, Methoden der jeweiligen
Klasse zu finden, die in diesem Fall vorhanden sind. Wenn die Methode nicht gefunden wird,
dann fängt Python an, die Methoden der zugehörigen Basisklassen eine nach der anderen
durchzugehen, in der Reihenfolge, wie sie in dem Tupel in der Klassendefinition angegeben ist.
Eine Anmerkung noch zur Sprechweise - wenn mehr als eine Klasse in dem Tupel der Basisklassen angegeben ist, von denen die Klasse erbt, dann spricht man von Mehrfachvererbung.
Wir haben nun die verschiedenen Aspekte von Klassen und Objekten erforscht und die damit verbundenen Sprechweisen kennen gelernt. Wir haben auch die Vorteile und möglichen Stolperfallen bei der objektorientierten Programmierung gesehen. Python ist hochgradig objektorientiert, und wenn wir diese Konzepte genau verstehen, wird uns dies langfristig sehr viel helfen.
Als Nächstes lernen wir, mit Ein/Ausgaben umzugehen und in Python auf Dateien zuzugreifen.
Klassenmethoden haben nur eine Besonderheit gegenüber gewöhnlichen Funktionen - sie müssen
einen zusätzlichen ersten Namen haben, der am Beginn der Parameterliste hinzugefügt wird,
dabei übergibt man diesem Parameter jedoch nicht
einen Wert, wenn man die Methode aufruft, sondern Python sorgt dafür. Diese spezielle Variable
repräsentiert das Objekt selbst, und es hat sich eingebürgert, ihr den Namen self
zu geben.
Man könnte diesem Parameter zwar irgendeinen Namen geben, aber es wird dennoch ausdrücklich
empfohlen, den Namen self
zu benutzen - jeder andere Name ist
an dieser Stelle eindeutig verpönt. Es hat viele Vorzüge, einen Standardnamen zu benutzen - jeder Leser
Ihres Programms wird ihn sofort erkennen, und spezielle IDEs (Integrierte
Entwicklungsumgebungen) können einen unterstützen, wenn man die Bezeichnung self
verwendet.
Anmerkung für C++/Java/C#-Programmierer
Das self
in Python entspricht dem self
-Zeiger in C++
und dem this
in Java und C#.
Sie fragen sich wahrscheinlich, auf welche Weise Python den Wert für self
übergibt und warum Sie dafür keinen Wert zu übergeben brauchen. Ein Beispiel wird dies klar machen.
Angenommen, Sie haben eine Klasse, die MeineKlasse
heißt und eine
Instanz dieser Klasse, die MeinObjekt
heißt. Wenn Sie eine Methode dieses
Objekts als MeinObjekt.methode(param1, param2)
aufrufen, dann wird dies von
Python automatisch in MeineKlasse.methode(MeinObjekt, param1, param2)
umgewandelt - und genau dafür ist dieses besondere self
da.
Das bedeutet auch, dass man selbst bei einer Methode, die keine Parameter entgegennimmt,
dennoch diese Methode mit einem self
-Parameter definieren muss.