Grundlegendes

Mit der Einführung der Klassen wird die Syntax ein klein wenig erweitert, es werden drei neue Objekt-Typen und einige neue Semantik eingeführt.

Syntax der Klassendefinition

Die einfachste Form der Klassendefinition sieht folgendermaßen aus:

class KlassenName:
    <Ausdruck-1>
    .
    .
    .
    <Ausdruck-N>

Klassendefinitionen, analog wie Funktionsdefinitionen (def Ausdrücke) müssen ausgeführt werden bevor sie irgendeinen Effekt haben. (Es ist zwar denkbar, eine Klassendefinition in eine if-Bedingung oder innerhalb einer Funktion zu definieren, macht aber eigentlich keinen Sinn.)

Normalerweise sind die Ausdrücke innerhalb einer Klasse Funktionsdefinitionen, dennoch sind andere Ausdrücke erlaubt und manchmal auch sinnvoll, dazu aber später. Die Funktionsdefinitionen innerhalb einer Klasse haben normalerweise eine besondere Art von Argumentliste, diktiert durch die Konventionen für den Aufruf von Methoden, siehe ebenfalls später.

Wenn eine Klassendefinition auftaucht, dann wird ein neuer Namensraum erstellt und als lokaler Gültigkeitsbereich hergenommen, d.h. dass alle Zuweisungen an lokale Variablen an diesen neuen Namensraum gebunden werden. Insbesondere binden auch Funktionsdefinitionen den Namen der Funktion an diesen neuen Namensraum.

Wenn eine Klassendefinition normal (über das Ende) beendet wird, dann wird hiermit ein Klassenobjekt erzeugt. Dies ist in erster Linie eine Hülle (wrapper) für den Inhalt des Namensraums, der durch die Klassendefinition erstellt wird. Der originäre lokale Gültigkeitsbereich (der vor der Klassendefinition aktiv war) wird wieder hergestellt und das Klassenobjekt wird an den Klassennamen gebunden der in der Klassendefinition definiert worden ist. (KlassenName in dem obigen Beispiel).

Klassen Objekte

Klassenobjekte unterstützen zwei Arten von Operationen: Attributreferenzen und Instanziierung.

Attributreferenzen

Attributreferenzen verwenden die Standard-Syntax, die für alle Verweise auf Attribute in Python verwendet wird: obj.name. Gültige Attributnamen sind alle Namen, die sich im Namemsraum der Klasse befinden, als das Klassenobjekt erstellt wurde. Wenn also die Klassendefinition folgendermaßen aussieht:

In [2]:
class MeineKlasse:
    """Eine einfache Beispielklasse"""
    i = 12345

    def f(self):
        return 'Hallo Welt'

dann sind

In [3]:
MeineKlasse.i 
Out[3]:
12345
In [4]:
MeineKlasse.f 
Out[4]:
<function __main__.MeineKlasse.f>

gültige Attributreferenzen, die einen Integerwert beziehungsweise ein Funktionsobjekt zurückgeben. Klassenattribute können durch eine Zuweisung ihren Wert ändern, z.B. den Wert von MeineKlasse.i.

In [5]:
MeineKlasse.__doc__
Out[5]:
'Eine einfache Beispielklasse'

ist ebenfalls ein gültiges Attribut und gibt den zur Klasse gehörenden Docstring zurück: Eine einfache Beispielklasse.

Klassen-Instanziierung

Die Klassen-Instanziierung verwendet eine Notation wie bei einer Funktion. Man kann sich z.B. das Klassenobjekt als eine parameterlose Funktion vorstellen, die eine neue Instanz der Klasse zurückgibt. Zum Beispiel (wenn man die obige Klasse verwendet):

In [6]:
x = MeineKlasse()

erzeugt eine neue Instanz der Klasse und ordnet dieses Objekt der lokalen Variablen x zu.

Der Instanziierungsvorgang (der Aufruf eines Klassenobjekts) erzeugt ein leeres Objekt. Viele Klassen benötigen jedoch Objekte mit Instanzen, die mit einem bestimmten Anfangszustand versehen sein müssen. Um dies zu ermöglichen, kann eine Klasse eine spezielle Methode mit dem Namen __init__() definieren:

def __init__(self):
    self.daten = []

Wenn eine Klasse eine __init__()-Methode definiert, ruft die Klasseninstanziierung automatisch __init__() für die neu erstellte Klasseninstanz auf. So kann in diesem Beispiel eine neue, initialisierte Instanz erhalten werden durch:

 x = MeineKlasse()

Die Methode __init__() kann für mehr Flexibilität auch Argumente haben. Für diesen Fall müssen die Argumente dem Klasseninstanziierungsoperator mitgegeben werden, damit dieser sie an __init__() weitergeben kann. Beispielsweise wird der folgenden Klasse Complex ein reeller und ein imaginärer Teil übergeben:

In [7]:
class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

c = Complex(3.0, -4.5)
c.r, c.i
Out[7]:
(3.0, -4.5)
In [8]:
%%Mooc StringAssesment
Out[8]:

Instanzen von Klassen

Es ist folgender Code gegeben



class MeineKlasse:
    "Eine einfache Beispielklasse"
    i = 12345

    def __init__(self):
        self.daten = []

    def f(self):
        return 'Hallo Welt'

x = MeineKlasse()
x.daten.append(x.f())
x.daten.append("hier ist Schwaben")
z = MeineKlasse()
print(z.daten)

Geben sie den Wert des Datenattributes daten der Instanz z an

Geben sie das Ergebnis des letzten print-Befehls als Antwort ein



Instanz Objekte

Was können wir nun mit Instanzobjekten anfangen? Die einzigen Operationen, die mit Instanzobjekten durchgeführt werden können, sind Attributreferenzen. Es gibt zwei Arten von gültigen Attributnamen, Attribute in denen Daten und Objekte gespeichert sind und Methoden.

Datenattribute entsprechen Instanzvariablen in anderen Programmiersprachen. Datenattribute müssen nicht deklariert werden; Wie lokale Variablen, werden sie dann erzeugt, wenn sie zuerst zugewiesen werden. Wenn beispielsweise x eine Instanz von MeineKlasse ist, die oben erstellt wurde, gibt das folgende Codeelement den Wert 16 aus, ohne eine Spur in der Klasse selbst zu hinterlassen:

In [9]:
x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter
16

Die andere Art einer Attributreferenz bei Instanzen ist die Methode. Eine Methode ist eine Funktion, die zu einem Objekt gehört. (In Python ist der Begriff Methode für Klasseninstanzen nicht eindeutig: Andere Objekttypen können auch Methoden haben. Listenobjekte haben z. B. Methoden namens append, insert, remove, sort, usw .. (wobei streng genommen Listenobjekte auch wiederum Instanzen einer Klasse list sind). In der folgenden Diskussion, verwenden wir den Begriff Methode ausschließlich, um Methoden von Klasseninstanzobjekten zu bezeichnen, sofern nicht explizit anders angegeben.)

Gültige Methodennamen eines Instanzobjekts hängen von seiner Klasse ab. Definitionsgemäß definieren alle Attribute einer Klasse, die Funktionsobjekte sind, entsprechende Methoden ihrer Instanzen. So ist in unserem Beispiel x.f eine gültige Methodenreferenz, da MeineKlasse.f eine Funktion ist, aber x.i nicht, da MeineKlasse.i keine ist. Aber x.f ist nicht dasselbe wie MeineKlasse.f - es ist ein Methodenobjekt, kein Funktionsobjekt.

Methoden Objekte

Eine Methode kann sofort nach der Instanziierung (d.h. nach dem Binden) aufgerufen werden:

In [10]:
x.f()
Out[10]:
'Hallo Welt'

In dem obigen Beispiel MeineKlasse wird dieser Aufruf den String 'Hallo Welt' zurückgeben. Es ist jedoch nicht notwendig, eine Methode sofort aufzurufen: x.f ist ein Methodenobjekt und kann zu einem späteren Zeitpunkt abgespeichert und aufgerufen werden. Beispielsweise wird

xf = x.f
while True:
    print(xf())

andauernd Hallo Welt bis zu einem Programmabbruch ausgeben.

Was genau geschieht, wenn eine Methode aufgerufen wird? Vielleicht haben Sie bemerkt, dass x.f() ohne ein Argument oben aufgerufen wurde, obwohl die Funktionsdefinition für f() ein Argument angegeben hat. Was ist mit diesem Argument geschehen ?

Tatsächlich können Sie die Antwort erraten haben: Das Besondere an Methoden ist, dass das Instanzobjekt als erstes Argument der Funktion übergeben wird. In unserem Beispiel ist der Aufruf x.f() genau gleichbedeutend mit MeineKlasse.f(x). Im Allgemeinen ist das Aufrufen einer Methode mit einer Liste von n Argumenten äquivalent zum Aufrufen der entsprechenden Funktion mit einer Argumentliste, die durch Einfügen des Instanzobjekts der Methode vor dem ersten Argument erzeugt wird.

Wenn Sie immer noch nicht verstehen, wie Methoden arbeiten, kann ein Blick auf die Umsetzung vielleicht Fragen klären. Wenn ein Instanzattribut referenziert wird, das kein Datenattribut ist, wird seine Klasse durchsucht. Wenn der Name ein gültiges Klassenattribut bezeichnet, das ein Funktionsobjekt ist, wird ein Methodenobjekt durch das Verbinden des Instanzobjekts mit dem soeben gefundenen Funktionsobjekts in einem abstrakten Objekt erzeugt: dies ist dann das Methodenobjekt. Wenn das Methodenobjekt mit einer Argumentliste aufgerufen wird, wird aus dem Instanzobjekt und der Argumentliste eine neue Argumentliste aufgebaut und das Funktionsobjekt mit dieser neuen Argumentliste aufgerufen.

Klassen und Instanz Variablen

Im Allgemeinen werden Instanzvariablen für Daten, die für jede Instanz eindeutig sind, verwendet und Klassenvariablen für Attribute und Methoden, die von allen Instanzen der Klasse geteilt werden:

In [11]:
class Hund:

    spezies = 'Canis lupus'    # Klassenvariable, die allen Instanzen gemeinsam ist

    def __init__(self, name):
        self.name = name       # Instanzvariable die für jede Instanz einzigartig ist

d = Hund('Joschi')
e = Hund('Alma')
print(d.spezies)               # gemeinsam für alle Hunde
print(e.spezies)               # gemeinsam für alle Hunde
print(d.name)                  # einzigartig für d
print(e.name)                  # einzigartig für e
Canis lupus
Canis lupus
Joschi
Alma

Wie in dem vorhergehenden Kapitel Namen und Objekte bereits angesprochen, können gemeinsam genutzte Daten möglicherweise überraschende Effekte bei der Verwendung von veränderlichen Objekten wie Listen und Dictionaries zur Folge haben. Zum Beispiel sollte die Liste tricks im folgenden Code nicht als Klassenvariable verwendet werden, da damit nur eine einzige Liste mit allen Hund-Instanzen geteilt werden würde:

In [12]:
class Hund:

    tricks = []             # falsche Benutzung einer Klassenvariablen
    
    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

d = Hund('Joschi')
e = Hund('Alma')
d.add_trick('Rolle')
e.add_trick('Toter Mann')
print(d.tricks)                    # unerwartete gemeinsame tricks
['Rolle', 'Toter Mann']

Das korrekte Design der Klasse sollte stattdessen eine Instanzvariable verwenden:

In [13]:
class Hund:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

d = Hund('Joschi')
e = Hund('Alma')
d.add_trick('Rolle')
e.add_trick('Toter Mann')
print(d.tricks)
print(e.tricks)
['Rolle']
['Toter Mann']
In [14]:
%%Mooc StringAssesment
Out[14]:

Klassenmethode

Gegeben sei der folgende Code:


import datetime
class Hund:

    def __init__(self, name, geburtsdatum, geschlecht="W"):
        self.name = name
        self.geburtsdatum = datetime.datetime.strptime(geburtsdatum,"%Y-%m-%d").date()
        self.geschlecht = geschlecht

    def alter(self):
        return x
        
d = Hund('Josie','2008-12-15')
e = Hund('Alma','2009-06-21')
f = Hund('Cooper','2010-04-13')
print(d.alter())
print(e.alter())
print(f.alter())

Berechnen sie das Alter der Hunde aus dem heutigen Tag und dem Geburtstag der Hunde. Der heutige Tag ist dabei mit dem Ausdruck


datetime.datetime.now()

zu bekommen. Jedes datetime-Objekt hat auch ein Datenattribut year

Geben sie den entsprechenden Ausdruck für x in der Methode alter ein



Weitere Anmerkungen

Datenattribute überschreiben Methodenattribute mit demselben Namen; um unbeabsichtigte Namenskonflikte zu vermeiden, die schwer zu findende Fehler in großen Programmen verursachen können, ist es ratsam, eine Art von Konvention zu verwenden, um die Chance von Konflikten zu minimieren. Mögliche Konventionen sind z.B. dass Methodennamen mit Großbuchstaben begonnen werden, oder dass Datenattributnamen mit einer kleinen eindeutigen Zeichenfolge (eventuell nur einem Unterstrich) vorangestellt werden oder das Verwenden von Verben für Methoden und Substantive für Datenattribute.

Datenattribute können sowohl durch Methoden als auch durch normale Benutzer ("Clients") eines Objekts referenziert werden. Mit anderen Worten, Klassen sind nicht zur Implementierung von reinen abstrakten Datentypen verwendbar. Tatsächlich können in Python keine Daten versteckt werden - alles basiert nur auf der Grundlage einer Konvention. (Auf der anderen Seite kann die Python-Implementierung, die in C geschrieben ist, die Implementierungsdetails vollständig verbergen und den Zugriff auf ein Objekt kontrollieren, falls dies erforderlich ist; dies kann für Erweiterungen zu Python verwendet werden, die in C geschrieben sind.)

Normale Benutzer ("Clients") sollten Datenattribute sorgfältig verwenden - Clients können durch Klassenmethoden verwaltete Invarianten durch ein Überschreiben von Datenattributen durcheinander bringen. Beachten Sie, dass Clients ihre eigenen Datenattribute einem Instanzobjekt hinzufügen können, ohne die Gültigkeit der Methoden zu beeinträchtigen, solange Namenskonflikte vermieden werden - auch für diese Problematik kann eine Namenskonvention viele Kopfschmerzen sparen.

Es gibt keine Abkürzung für das Referenzieren von Datenattributen innerhalb von Methoden. Dies erhöht tatsächlich die Lesbarkeit von Methoden: Es gibt keine Möglichkeit lokale Variablen und Instanzvariablen beim Durchsehen einer Methode zu verwechseln.

Oft wird das erste Argument einer Methode self genannt. Dies ist nichts weiter als eine Konvention: Der Name self hat für Python absolut keine besondere Bedeutung. Beachten Sie jedoch, dass, wenn Sie nicht der Konvention folgen, Ihr Code weniger lesbar für andere Python-Programmierer sein kann, und es ist auch denkbar, dass ein Klassenbrowser-Programm geschrieben werden könnte, der auf einer solchen Konvention basiert.

Jedes Funktionsobjekt, das ein Klassenattribut ist, definiert eine Methode für Instanzen dieser Klasse. Es ist nicht notwendig, dass die Funktionsdefinition in der Klassendefinition textuell eingeschlossen ist: Die Zuweisung eines Funktionsobjekts zu einer lokalen Variablen in der Klasse ist ebenfalls ok. Beispielsweise:

Funktionsdefinition außerhalb der Klasse

In [15]:
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1

    def g(self):
        return 'hello world'

    h = g

Hierin sind f, g und h alles Attribute der Klasse C, die sich auf Funktionsobjekte beziehen und folglich sind alle Methoden von Instanzen von C - h ist dabei äquivalent zu g. Beachten Sie, dass diese Praxis nur dazu dient, den Leser eines Programms zu verwirren.

Methodenaufrufe innerhalb der Klasse

Methoden können andere Methoden mit Methodenattributen des Arguments self aufrufen:

In [16]:
class Bag:
    def __init__(self):
        self.data = []

    def add(self, x):
        self.data.append(x)

    def addtwice(self, x):
        self.add(x)
        self.add(x)

Methoden können globale Namen auf dieselbe Weise wie normale Funktionen verwenden. Der globale Gültigkeitsbereich, der mit einer Methode verbunden ist, ist das Modul, das seine Definition enthält. (Eine Klasse wird niemals als globaler Gültigkeitsbereich verwendet.) Während man selten einen guten Grund für die Verwendung von globalen Daten in einer Methode findet, gibt es viele legitime Nutzungsmöglichkeiten des globalen Gültigkeitsbereichs: Zum einen können Funktionen und Module, die in den globalen Gültigkeitsbereich importiert werden, genauso von Methoden verwendet werden, wie Funktionen und Klassen die in ihm definiert werden. Normalerweise wird die Klasse, die die Methode enthält, selbst in diesem globalen Bereich definiert, und im nächsten Abschnitt finden wir einige gute Gründe, warum eine Methode auf ihre eigene Klasse verweisen möchte.

Jeder Wert ist ein Objekt und hat daher eine Klasse (auch als Typ bezeichnet). Es wird als objekt.__class__ gespeichert.

In [17]:
%%Mooc Video
Out[17]:

Weitere Literatur

In [18]:
%%Mooc WebReference

A First Look at Classes

https://docs.python.org/3/tutorial/classes.html#a-first-look-at-classes

Hinweis: Eine erste Betrachtung von Klassen

In [19]:
%%Mooc WebReference

Random Remarks

https://docs.python.org/3/tutorial/classes.html#random-remarks

Hinweis: Weitere Anmerkungen zu Klassen