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')