Vorlesungsskript 27
Klassen
Python stellt mehrere mögliche Werkzeuge zur Verfügung, um eigene Datentypen zu definieren. Das wichtigste sind Klassen. Sie sind objektorientierten Programmiersprachen wie z.B. Java oder C# entlehnt. Man definiert eine Klasse und legt darin die grundlegenden Eigenschaften der Objekte dieser Klasse fest. Die Klasse ist dann unser Datentyp und erlaubt es uns, beliebig viele Objekte dieses neuen Typs zu erzeugen.
Beginnen wir mit einem einfachen Beispiel: einer Klasse namens Duck
.
Jedes Objekt dieser Klasse soll eine Ente repräsentieren. Python-Klassendefinitionen
haben einen Kopf und einen Körper, der Kopf wird mit class
eingeleitet
und der Körper enthält hauptsächlich Methodendefinitionen. Als erstes Beispiel
definieren wir die Klasse Duck
noch ohne Methoden. Wir zeigen mit
pass
an, dass der Körper leer ist:
class Duck:
pass
Nun können wir unsere ersten Entenobjekte er- und uns davon überzeugen, dass
sie vom Typ Duck
sind und dass sie verschieden sind:
>>> d1 = Duck()
>>> d2 = Duck()
>>> isinstance(d1, Duck)
True
>>> isinstance(d2, Duck)
True
>>> d1 == d2
False
Der Ausdruck Duck()
ist ein
Konstruktoraufruf. Konstruktoraufrufe bestehen aus dem Namen einer
Klasse und einem Paar runder Klammern mit einer Argumentliste (die in unserem
Beispiel noch leer ist). Ein Konstrukturaufruf erzeugt ein neues Objekt dieser
Klasse und gibt es zurück.
Nun wäre es schön, wenn unsere Enten mit uns kommunizieren könnten. Dazu definieren wir
die erste Methode: speak
. Nun sieht unsere Klassendefinition so aus:
class Duck:
def speak(self):
print('Quack')
Methodendefinitionen bestimmen, welche Methoden für die Objekte einer Klasse zur Verfügung
stehen. Sie sehen aus wie Funktionsdefinitionen, befinden sich aber innerhalb des Körpers
einer Klassendefinition und haben einen speziellen ersten Parameter, der per Konvention immer
self
heißt. An diesen Parameter wird beim Aufruf der Methode automatisch
das Objekt selbst übergeben.
Nun können wir die speak
-Methode unserer Duck
-Objekte
aufrufen:
>>> d1 = Duck()
>>> d2 = Duck()
>>> d1.speak()
Quack
>>> d2.speak()
Quack
Beachten Sie, dass dem self
-Parameter hierbei
automatisch das Objekt übergeben wird, das vor dem Punkt steht. Wir müssen und
dürfen für diesen Parameter kein Argument in der Argumentliste übergeben.
Um unsere Enten unterscheiden zu können, wollen wir ihnen nun Namen geben.
Die Idee ist, dass wir dem Konstruktoraufruf einen Namen übergeben. Dieser wird dann
in dem Entenobjekt gespeichert und wir können über ein Attribut namens
name
darauf zugreifen. Attribute sind wie „Variablen innerhalb von Objekten“.
Sie enthalten die Daten, die zu einem Objekt gehören, und man kann mit Hilfe des .
-Operators
darauf zugreifen, ähnlich wie auf Methoden. Wir wollen also das Folgende tun können:
>>> d1 = Duck('Hans')
>>> d2 = Duck('Susi')
>>> d1.name
'Hans'
>>> d2.name
'Susi'
Um das möglich zu machen, müssen wir unserer Klasse einen
Konstruktor geben. Unsere bisherigen Konstruktoraufrufe gingen an
den Standard-Konstruktor, und der nimmt keine Argumente. Ein Konstruktor ist
eine spezielle Methode mit dem Namen __init__
, die
beim Erzeugen des Objekts automatisch aufgerufen wird. Sie erhält nach self
auch alle Argumente, die an den Konstruktoraufruf
übergeben werden, also in unserem Beispiel den Namen der Ente. Konstruktoren
dienen in erster Linie dazu, solche Argumente in Attributen zu speichern. Wir
erweitern unsere Klassendefinition also wie folgt:
class Duck:
def __init__(self, name):
self.name = name
def speak(self):
print('Quack')
Der Konstruktor macht nichts anderes, als den übergebenen Namen im Attribut name
des Objekts zu speichern. Nun funktioniert obiger Code.
Zum Abschluss unseres kurzen Eintauchens in Klassendefinitionen sorgen wir noch dafür, dass Python unsere Enten-Objekt auch vernünftig ausgeben kann. Bisher ist es nämlich so:
>>> l1 = ['schwimmen', 'auf', 'dem', 'See']
>>> d1 = Duck('Daffy')
>>> l1
['schwimmen', 'auf', 'dem', 'See']
>>> d1
<Duck object at 0x7fd90f0cb780>
Bei eingebauten Datentypen wie z.B. der Liste in diesem Beispiel weiß der
Python-Interpreter, wie er sie so darstellen kann, dass man den Inhalt des
Objekts sieht. Bei unserem Enten-Objekt ist das noch nicht der Fall, es wird
nur ein String mit der Klasse und der Speicheradresse unseres Objekts
ausgegeben. Wir können für eine informativere Darstellung sorgen, indem wir in
unserer Klasse die spezielle __repr__
-Methode
implementieren. Diese Methode lassen wir einen String zurückgeben, der das
jeweilige Objekt repräsentiert, und zwar in Form eines Python-Ausdrucks, der
ein gleiches Objekt erzeugen würde (hier: eines Konstruktoraufrufs). Unsere
Klasse sieht nun so aus:
class Duck:
def __init__(self, name):
self.name = name
def speak(self):
print('Quack')
def __repr__(self):
return 'Duck(' + repr(self.name) + ')'
Der Ausdruck repr(self.name)
erzeugt aus dem
Namen der Ente ein String-Literal.
Nun braucht sich die Darstellung unserer Enten-Objekte im Interpreter nicht mehr hinter denen von eingebauten Datentypen zu verstecken:
>>> l1 = ['schwimmen', 'auf', 'dem', 'See']
>>> d1 = Duck('Daffy')
>>> l1
['schwimmen', 'auf', 'dem', 'See']
>>> d1
Duck('Daffy')