Unterabschnitte


9. Klassen

Der Klassen-Mechanismus von Python fügt Klassen mit einem Minimum an neuer Syntax und Semantik der Sprache hinzu. Es ist eine Mischung von Klassen-Mechanismen, wie sie in C++ und Modula-3 anzutreffen sind. Genau wie bei Modulen, setzen Klassen in Python keine absolute Grenze zwischen Definition und Benutzung, sondern verlassen sich eher auf die Vernunft des Benutzers, nicht ,,in die Definition einzubrechen.`` Die wichtigsten Eigenschaften von Klassen werden jedoch mit voller Kraft erhalten: der Klassen-Vererbungs-Mechanismus erlaubt Mehrfach-Vererbung von Klassen, eine abgeleitete Klasse kann Methoden ihrer Oberklasse (oder -Klassen) überschreiben, eine Methode kann die Methode gleichen Namens in der Oberklasse aufrufen. Objekte können beliebige Mengen privater Daten enthalten.

In C++-Terminologie ausgedrückt sind alle Klassenmitglieder (engl. class members), inklusive der Datenmitglieder, öffentlich, und alle Mitglieder-Funktionen sind virtuell. Es gibt keine speziellen Konstruktoren oder Destruktoren. Wie in Modula-3 gibt es keine Kurzformen, um die Mitglieder von Objekten von ihren Methoden zu referenzieren: die Methodenfunktion wird mit einem expliziten ersten Argument deklariert, das das Objekt repräsentiert, welches implizit beim Aufruf übergeben wird. Wie in Smalltalk sind Klassen ihrerseits Objekte, allerdings im weiteren Sinne des Wortes: in Python sind alle Datentypen Objekte. Das stellt die Semantik beim Import und der Umbenennung dar. Aber, genau wie in C++ oder Modula-3, können eingebaute Datentypen nicht als Oberklassen für die Erweiterung durch die Benutzerin verwendet werden. Außerdem können wie in C++, aber nicht in Modula-3, die meisten eingebauten Operatoren mit spezieller Syntax (arithmetische Operatoren, Indizierung, etc.) in Instanzen von Klassen umdefiniert werden.


9.1 Ein Wort zur Terminologie

Da es keine universell anerkannte Terminologie gibt, in der man über Klassen sprechen könnte, werde ich gelegentlich Begriffe aus der Smalltalk- und C++-Welt verwenden. (Ich würde gern Begriffe aus Modula-3 verwenden, da seine objektorientierte Semantik Python näher steht, als die von C++, aber ich vermute, daß weniger Leser davon gehört haben.)

Ich muß Sie auch dahingehend warnen, daß es eine begriffliche Falle für Leserinnen mit Erfahrung in objektorientierter Programmierung gibt: das Wort ,,Objekt`` bedeutet in Python nicht notwendigerweise eine Klassen-Instanz. Wie in C++ und Modula-3, und im Gegensatz zu Smalltalk sind nicht alle Typen in Python mit Klassen gleichzusetzen: die fundamentalen, eingebauten Typen wie Ganzzahlen und Listen sind keine, und auch etwas exotischere Typen wie Dateien sind keine. Alle Python-Typen jedoch haben eine gewisse Semantik gemein, die am besten mit dem Wort Objekt beschrieben werden kann.

Objekte haben ein eigenständiges Dasein, und mehrere Namen (in mehreren Geltungsbereichen) können mit dem gleichen Objekt verbunden sein. Das ist in anderen Sprachen als aliasing (in etwa: Pseudonyme) bekannt. In einer ersten flüchtigen Betrachtung von Python wird das normalerweise nicht besonders gewürdigt und kann gefahrlos ignoriert werden, wenn man es mit unveränderlichen Grund-Datentypen zu tun hat (Zahlen, Strings, Tupel). Aliasing hat jedoch einen (beabsichtigten!) Effekt auf die Semantik von Python-Code, auch wenn veränderliche Objekte wie Listen, Dictionaries und die meisten Typen, die Einheiten außerhalb des Programmes repräsentieren (Dateien, Fenster, etc.). Das wird normalerweise von einem Programm vorteilhaft eingesetzt, da solche Aliase sich in vielerlei Hinsicht wie Zeiger verhalten. Die Übergabe eines Objektes z.B. ist eine einfache Operation, da die Implementation von Python nur einen Zeiger übergibt; und wenn eine Funktion ein als Argument übergebenes Objekt modifiziert, wird der Aufrufer die Modifikation sehen - das macht zwei verschiedene Übergabe-Mechanismen für Argumente, wie etwa in Pascal überflüssig.


9.2 Geltungsbereiche und Namensräume in Python

Bevor Klassen eingeführt werden, muß ich Ihnen erst etwas über die Regeln für Geltungsbereiche in Python erzählen. Klassen-Definitionen spielen mit ein paar niedlichen Tricks in Namensräumen, und sie müssen wissen, wie Geltungsbereiche und Namensräume funktionieren, um zu verstehen, was vor sich geht. Und schließlich ist dieses Wissen auch hilfreich für jeden fortgeschrittenen Python-Programmierer.

Fangen wir mit ein paar Definitionen an.

Ein Namensraum ist eine Abbildung von Namen nach Objekten. Die meisten Namensräume sind im Moment als Python-Dictionaries implementiert, aber davon bemerkt man man normalerweise nichts (außer bei der Laufzeit-Geschwindigkeit), und es könnte sich in Zukunft ändern. Beispiele für Namensräume sind: die Menge eingebauter Namen (Funktionen wie z.B. abs() und Namen eingebauter Ausnahmen), die globalen Namen in einem Modul und die lokalen Namen bei einem Funktionsaufruf. In gewissem Sinne bildet die Menge der Attribute eines Objektes auch einen Namensraum. Die wichtigste Eigenschaft von Namensräumen, die man kennen muß, ist die, daß es absolut keinen Zusammenhang zwischen Namen in verschiedenen Namensräumen gibt. Zum Beispiel dürfen zwei verschiedene Module eine Funktion namens ,,maximize`` definieren, ohne daß es irgendwelche Probleme damit gäbe - Benutzerinnen der Module müssen sie mit dem Modulnamen als Präfix versehen.

Übrigens verwende ich das Wort Attribut für alle Namen, die nach einem Punkt stehen. In dem Ausdruck z.real ist real ein Attribut des Objektes z. Streng genommen sind Referenzen auf Namen in Modulen Attribut-Referenzen: in dem Ausdruck modname.funcname, ist modname ein Modul-Objekt und funcname ist ein Attribut davon. In diesem Fall gibt es eine simple Abbildung zwischen den Attributen des Moduls und den im Modul definierten globalen Namen: sie teilen sich den gleichen Namensraum!9.1

Attribute dürfen nur-lesbar oder schreibbar sein. In letzterem Fall ist die Zuweisung an Attribute erlaubt. Modul-Attribute sind schreibbar: Sie können schreiben: "modname.the_answer = 42". Schreibbare Attribute dürfen auch mit der del-Anweisung gelöscht werden, z.B. "del modname.the_answer".

Namensräume werden zu verschiedenen Zeitpunkten erzeugt und haben verschiedene Lebenszeiten. Der Namensraum, der die eingebauten Namen enthält, wird erzeugt, wenn der Python-Interpreter gestartet wird, und wird niemals gelöscht. Der globale Namensraum eines Moduls wird erzeugt, wenn die Definition des Moduls eingelesen wird. Normalerweise existieren Modul-Namensräume auch, bis der Interpreter terminiert. Die Anweisungen, die in der obersten Ebene des Interpreters ausgeführt werden (entweder aus einem Skript oder aus einer Sitzung interaktiv gelesen), werden als Teil eines Moduls namens __main__ betrachtet, so daß sie ihren eigenen globalen Namensraum haben. (Die eingebauten Namen existieren ebenfalls in einem Modul. Es wird __builtin__ genannt.)

Der lokale Namensraum für eine Funktion wird erzeugt, wenn die Funktion aufgerufen wird, und gelöscht, wenn sie einen Wert zurück gibt, oder eine Ausnahme auslöst, die nicht in der Funktion behandelt wird. (Tatsächlich wäre ,,vergessen`` das bessere Wort, um zu beschreiben, was eigentlich passiert.) Natürlich haben rekursive Aufrufe alle ihre eigenen Namensräume.

Ein Geltungsbereich ist ein Bereich des Quelltextes eines Python-Programmes (bisher nur Code genannt), wo ein Namensraum direkt zugänglich ist. ,,Direkt zugänglich`` bedeutet hier, daß versucht wird, eine nicht-qualifizierte Referenz auf einen Namen innerhalb dieses Namensraumes zu finden.

Obwohl Geltungsbereiche statisch bestimmt werden, ist ihre Verwendung dynamisch. Zu jedem Zeitpunkt während der Ausführung werden genau drei verschachtelte Namensräume verwendet (d.h. es sind genau drei Namensräume direkt zugänglich): der innerste Geltungsbereich, in dem als erstes gesucht wird, enthält die lokalen Namen, der mittlere Geltungsbereich, in dem anschließend gesucht wird, enthält die globalen Namen des aktuellen Moduls, und der äußerste Geltungsbereich (wo zuletzt gesucht wird) ist der Namensraum, der eingebaute Namen enthält.

Normalerweise referenziert der lokale Geltungsbereich die lokalen Namen der (textuell) aktuellen Funktion. Außerhalb von Funktionen referenziert der lokale Geltungsbereich den gleichen Namensraum wie der globale Geltungsbereich: den Modul-Namensraum. Klassen-Definitionen fügen dem lokalen Geltungsbereich einen weiteren Namensraum hinzu.

Es ist wichtig, zu begreifen, daß Geltungsbereiche textuell bestimmt werden: der globale Geltungsbereich einer Funktion, die in einem Modul definiert wurde, ist der Namensraum dieses Moduls, unabhängig davon, von wo und mit welchem Alias die Funktion aufgerufen wird. Auf der anderen Seite vollzieht sich die tatsächliche Suche nach Namen dynamisch, d.h. zur Laufzeit. Allerdings entwickelt sich die Sprachdefinition in Richtung von statischer Namens-Auflösung, d.h. zur Zeit der ,,Übersetzung`` - verlassen Sie sich also nicht auf dynamische Namens-Auflösung! (Tatsächlich werden lokale Variablen bereits statisch bestimmt.)

Ein besonderer Umstand von Python ist der, daß sich Zuweisungen immer im innersten Geltungsbereich abspielen. Zuweisungen kopieren keine Daten, sie binden lediglich Namen an Objekte. Das gleiche gilt für Lösch-Operationen: die Anweisung "del x" entfernt die Bindung von x vom Namensraum, der im lokalen Geltungsbereich referenziert wird. Tatsächlich, verwenden alle Operationen, die neue Namen einführen, den lokalen Geltungsbereich: insbesondere binden import-Anweisungen und Funktionsdefinitionen den Modul- oder Funktionsnamen an den lokalen Geltungsbereich. (Die global-Anweisung kann verwendet werden, um anzuzeigen, daß gewisse Variablen im globalen Geltungsbereich zuhause sind.)


9.3 Ein erster Blick auf Klassen

Klassen führen ein wenig neue Syntax, drei neue Objekt-Typen und ein wenig neue Semantik ein.


9.3.1 Syntax der Klassen-Definition

Die einfachste Form der Klassen-Definition sieht so aus:


class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

Klassen-Definitionen müssen wie Funktionsdefinitionen (def-Anweisungen) auch ausgeführt werden, bevor sie einen Effekt haben können. (Man könnte sich etwa vorstellen, eine Klassen-Definition in einen Zweig einer if-Anweisung oder innerhalb einer Funktion zu plazieren.)

In der Praxis werden die Anweisungen innerhalb einer Klassen-Definition normalerweise Funktionsdefinitionen sein, aber andere Anweisungen sind auch erlaubt und manchmal auch nützlich - wir werden später noch einmal darauf zurück kommen. Die Funktionsdefinitionen innerhalb einer Klasse haben normalerweise eine eigentümliche Form einer Argumentliste, die von den Aufrufkonventionen für Methoden diktiert wird - auch das wird später erklärt.

Wenn eine Klassen-Definition betreten wird, wird ein neuer Namensraum erzeugt und als lokaler Geltungsbereich verwendet - daher gehen alle Zuweisungen an lokale Variablen in diesen neuen Namensraum. Insbesondere binden Funktionsdefinitionen den Namen einer neuen Funktion hier an.

Wird eine Klassen-Definition normal verlassen (also am Ende), so wird ein Klassen-Objekt erzeugt. Dies ist im wesentlichen eine Hülle um den Inhalt des Namensraumes, der von der Klassen-Definition erzeugt wird. Wir werden im nächsten Abschnitt noch mehr über Klassen-Objekte erfahren. Der ursprüngliche lokale Geltungsbereich (derjenige, der aktuell war, gerade bevor die Klassen-Definition betreten wurde), wird wiedereingesetzt, und das Klassen-Objekt wird hier an den Klassennamen gebunden, der im Kopf der Klassen-Definition angegeben wurde (ClassName im Beispiel).


9.3.2 Klassen-Objekte

Klassen-Objekte unterstützen zwei Arten von Operationen: Attribut-Referenzen und Instanziierung .

Attribut-Referenzen verwenden die Standard-Syntax, die für alle Attribut-Referenzen in Python verwendet wird: obj.name. Gültige Attributnamen sind all die Namen, die im Namensraum der Klasse waren, als das Klassen-Objekt erzeugt wurde. Wenn also die Klassen-Definition so aussah:


class MyClass:
    "A simple example class"
    i = 12345
    def f(x):
        return 'hello world'

dann sind MyClass.i und MyClass.f gültige Attribut-Referenzen, die jeweils eine Ganzzahl und ein Funktions-Objekt zurück geben. An Klassenattribute kann auch zugewiesen werden, so daß man den Wert von MyClass.i mittels Zuweisung ändern kann. __doc__ ist auch ein gültiges Attribut, das nur-lesbar ist und den zur Klasse gehörenden Dokumentations-String zurück gibt: "A simple example class").

Die Instanziierung von Klassen verwendet die Funktions-Notation. Tun Sie einfach so, als ob das Klassen-Objekt eine parameterlose Funktion wäre, die eine neue Instanz der Klasse zurück gibt. Zum Beispiel erzeugt (unter Verwendung obiger Klasse):


x = MyClass()

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


9.3.3 Instanz-Objekte

Was können wir nun mit Instanz-Objekten tun? Die einzigen Operationen, die von Instanz-Objekten verstanden werden, sind Attribut-Referenzen. Es gibt zwei Arten gültiger Attributnamen.

Die ersten werde ich Datenattribute nennen. Diese entsprechen den ,,Instanzvariablen`` in Smalltalk, und den ,,Datenmitgliedern`` in C++. Datenattribute müssen nicht deklariert werden, sondern sie werden wie lokale Variablen erzeugt, wenn ihnen zum ersten mal etwas zugewiesen wird. Wenn z.B. x eine Instanz der oben definierten Klasse MyClass ist, wird der folgende Code klaglos den Wert 16 ausgeben:


x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print x.counter
del x.counter

Die zweite Art von Attribut-Referenzen sind Methoden. Eine Methode ist eine Funktion, die zu einem Objekt ,,gehört.`` (In Python gilt der Begriff Methode nicht ausschließlich für Klassen-Instanzen: andere Objekt-Typen können ebenfalls Methoden haben. Listen-Objekte z.B. haben Methoden namens append, insert, remove, sort, u.s.w. Im weiteren werden wir jedoch den Begriff Methode nur ausschließlich verwenden, um darunter Methoden von Instanzen von Klassen-Objekten zu verstehen, außer wir drücken explizit etwas anderes aus.)

Gültige Methodennamen eines Instanz-Objektes hängen von seiner Klasse ab. Per definitionem definieren alle Attribute einer Klasse, die (benutzerdefinierte) Funktions-Objekte darstellen, entsprechende Methoden ihrer Instanzen. In unserem Beispiel ist x.f eine gültige Methoden-Referenz, da MyClass.f eine Funktion ist, aber x.i ist keine, da MyClass.i keine Funktion ist. Aber x.f ist nicht das gleiche wie MyClass.f - es ist ein Methoden-Objekt, kein Funktions-Objekt.


9.3.4 Methoden-Objekte

Normalerweise wird eine Methode sofort aufgerufen, z.B.:


x.f()

In unserem Beispiel würde das den String 'hello world'zurück geben. Es ist jedoch nicht notwendig, eine Methode direkt aufzurufen: x.f ist ein Methoden-Objekt und kann anderweitig gespeichert und später aufgerufen werden. Zum Beispiel wird:


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

"hello world" bis zum Ende aller Tage ausgeben.

Was passiert genau, wenn eine Methode aufgerufen wird? Sie werden vielleicht bemerkt haben, daß x.f() oben ohne ein Argument aufgerufen wurde, selbst, wenn die Funktionsdefinition für f ein Argument spezifizierte. Was geschah mit dem Argument? Natürlich löst Python eine Ausnahme aus, wenn eine Funktion, die eine Argument verlangt, ohne ein solches aufgerufen wird - selbst, wenn das Argument tatsächlich gar nicht verwendet wird...

Vielleicht haben Sie es bereits erraten: das Besondere an Methoden ist, daß das Objekt als erstes Argument der Funktion übergeben wird. In unserem Beispiel ist der Aufruf von x.f() exakt äquivalent zu MyClass.f(x). Allgemein gilt, daß der Aufruf einer Methode mit einer Liste von n Argumenten dazu äquivalent ist, die entsprechende Funktion mit einer Argumentliste aufzurufen, die gebildet wird, indem das Objekt der Methode vor dem ersten Argument eingefügt wird.

Wenn Sie noch nicht verstehen, wie Methoden funktionieren, kann ein Blick in die Implementierung die Dinge vielleicht klären. Wenn ein Instanzattribut referenziert wird, das kein Datenattribut ist, wird seine Klasse abgesucht. Wenn der Name ein gültiges Klassenattribut bezeichnet, das ein Funktions-Objekt ist, wird ein Methoden-Objekt erzeugt, indem (Zeiger auf) das Instanz-Objekt und das Funktions-Objekt in ein abstraktes Objekt verpackt werden: das Methoden-Objekt. Wenn das Methoden-Objekt mit einer Argumentliste aufgerufen wird, wird es wieder ausgepackt, eine neue Argumentliste wird vom Instanz-Objekt und der ursprünglichen Argumentliste gebildet, und das Funktions-Objekt wird mit dieser neuen Argumentliste aufgerufen.


9.4 Diverse Bemerkungen

[Diese sollten vielleicht sorgfältiger plaziert werden...]

Datenattribute überschreiben Methodenattribute mit dem gleichen Namen. Um versehentliche Namenskonflikte zu vermeiden, die in großen Programmen schwer zu findende Fehler verursachen können, ist es ratsam, irgendeine Konvention zu verwenden, die die Chancen für solche Konflikte minimiert, z.B. Methodennamen mit großen Anfangsbuchstaben beginnen zu lassen, Datenattribute mit einem eindeutigen String-Präfix zu versehen (vielleicht nur ein Unterstrich), oder Verben für Methoden und Substantive für Datenattribute zu verwenden.

Datenattribute dürfen von Methoden genauso wie von gewöhnlichen Benutzern (,,Klienten``) eines Objektes referenziert werden. Mit anderen Worten, Klassen sind unbrauchbar, um pure abstrakte Datentypen zu implementieren. Tatsächlich gibt es in Python keine Möglichkeit, Datenkapselung zu erzwingen - es basiert alles auf Konventionen. (Auf der anderen Seite kann die in C geschriebene Python-Implementation, Implementierungsdetails vollständig verbergen und den Zugang zu einem Objekt kontrollieren, wenn notwendig. Das kann von Python-Erweiterungen verwendet werden, die in C geschrieben wurden.)

Klienten sollten Datenattribute vorsichtig verwenden - Klienten könnten Invarianten durcheinanderbringen, die von Methoden verwaltet werden, indem sie auf deren Datenattributen herumtrampeln . Man beachte, daß Klienten eigene Datenattribute zu einem Instanz-Objekt hinzufügen können, ohne die Gültigkeit der Methoden zu beeinflussen, solange wie Namenskonflikte vermieden werden. Wieder kann hier einem eine Namenskonvention eine Menge Kopfschmerzen ersparen.

Es gibt keine Kurzform, um Datenattribute (oder andere Methoden!) aus Methoden heraus zu referenzieren. Ich finde, daß das die Lesbarkeit von Methoden eher erhöht: es gibt keine Möglichkeit, beim schnellen Durchschauen einer Methode, lokale Variablen mit Instanzvariablen zu verwechseln.

Gewöhnlich wird das erste Argument einer Methode selfgenannt. Dies ist nichts weiter als eine Konvention: der Name self hat absolut keine spezielle Bedeutung für Python. (Man beachte jedoch, daß bei Nichtbeachtung der Konvention Ihr Code für andere Python-Programmierer weniger lesbar sein könnte, und es ist auch vorstellbar, daß ein Klassen-Browser geschrieben wird, der sich auf diese Konvention verläßt.)

Jedes Funktions-Objekt, das ein Klassenattribut ist, definiert eine Methode für Instanzen dieser Klasse. Es ist nicht notwendig, daß die Funktionsdefinition textuell in der Klassen-Definition eingeschlossen ist. Ein Funktions-Objekt an eine lokale Variable in der Klasse zuzuweisen ist auch ok. Zum Beispiel:


# Funktion, die ausserhalb der Klasse definiert ist.
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'hello world'
    h = g

Nun sind f, g und h alles Attribute der Klasse C, die sich auf Funktions-Objekte beziehen, und dadurch sind sie alle Methoden von Instanzen von C - wobei h exakt äquivalent zu g ist. Man beachte, daß dieses Vorgehen normalerweise nur dazu dient, die Leserin zu verwirren.

Methoden dürfen andere Methoden aufrufen, indem sie Methodenattribute des self-Arguments verwenden, z.B.:


class Bag:
    def empty(self):
        self.data = []
    def add(self, x):
        self.data.append(x)
    def addtwice(self, x):
        self.add(x)
        self.add(x)

Die Instanziierungs-Operation (ein Klassen-Objekt ,,aufzurufen``) erzeugt ein leeres Objekt. Viele Klassen wollen Objekte mit einem bekannten Anfangszustand erzeugen. Daher darf eine Klasse eine spezielle Methode namens __init__() wie folgt definieren:


    def __init__(self):
        self.empty()

Wenn eine Klasse eine __init__()-Methode definiert, wird bei der Klassen-Instanziierung automatisch die Methode __init__() für die neu erzeugte Klassen-Instanz aufgerufen. Im Bag-Beispiel kann man eine neue, initialisierte Instanz wie folgt erhalten:


x = Bag()

Natürlich darf die Methode __init__() Argumente haben, und so eine größere Flexibilität haben. In diesem Fall werden Argumente, die an den Klassen-Instanziierungsoperator übergeben werden, an __init__() weitergeben. Beispiel:


>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0,-4.5)
>>> x.r, x.i
(3.0, -4.5)

Methoden dürfen globale Namen genau wie gewöhnliche Funktionen referenzieren. Der globale Geltungsbereich, der mit einer Methode assoziiert ist, ist das Modul, das die Klassen-Definition enthält. (Die Klasse selbst wird nie als globaler Geltungsbereich verwendet!) Während man selten einen guten Grund für die Verwendung von globalen Daten in einer Methode findet, gibt es viele berechtigte Anwendungen für den globalen Geltungsbereich: einerseits können Funktionen und Module, die in den globalen Geltungsbereich importiert werden, von anderen Methoden und Funktionen, die darin definiert werden, benutzt werden. Normalerweise ist die Klasse, die die Methode definiert, selbst im globalen Geltungsbereich definiert, und im nächsten Abschnitt werden wir einige gute Gründe dafür finden, daß eine Methode ihre eigene Klasse referenzieren will.


9.5 Vererbung

Natürlich würde eine Spracheigenschaft den Namen ,,Klasse`` nicht verdienen, ohne Vererbung zu unterstützen. Die Syntax für eine abgeleitete Klassen-Definition sieht wie folgt aus:


class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

Der Name BaseClassName muß in einem Geltungsbereich definiert werden, der die abgeleitete Klassen-Definition umfaßt. Anstelle eines Namens für eine Oberklasse ist auch ein Ausdruck erlaubt. Das ist hilfreich, wenn die Oberklasse in einem anderen Modul definiert wird, z.B.:


class DerivedClassName(modname.BaseClassName):

Die Ausführug einer abgeleiteten Klassen-Definition verläuft genau wie die einer Oberklasse. Wenn das Klassen-Objekt konstruiert wird, wird die Oberklasse vermerkt. Diese wird verwendet, um Attribut-Referenzen aufzulösen: wenn ein gesuchtes Attribut in der Klasse nicht gefunden wird, wird es in der Oberklasse gesucht. Diese Regel wird rekursiv angewandt, wenn die Oberklasse ihrerseits von einer anderen Klasse abgeleitet ist.

Es gibt nichts besonderes bei der Instanziierung von abgeleiteten Klassen: DerivedClassName() erzeugt eine neue Instanz der Klasse. Methoden-Referenzen werden wie folgt aufgelöst: das entsprechende Klassenattribut wird gesucht, wobei die Kette von Oberklassen hinaufgestiegen wird, wenn notwendig. Wenn dies zu einem Funktions-Objekt führt, ist die Methoden-Referenz gültig.

Abgeleitete Klassen dürfen Methoden ihrer Oberklassen überschreiben. Da Methoden keine speziellen Privilegien beim Aufruf anderer Methoden desselben Objektes haben, kann eine Methode einer Oberklasse, die eine andere in derselben Oberklasse definierte Methode aufruft, schließlich tatsächlich eine Methode einer abgeleiteten Klasse aufrufen, die sie überschreibt. (Für C++-Programmierer: alle Methoden in Python sind ,,virtuelle Funktionen``.)

Eine überschreibende Methode in einer abgeleiteten Klasse könnte aber eine Methode gleichen Namens in der Oberklasse auch erweitern, anstatt sie ersetzen zu wollen. Es gibt eine einfache Art, die Methode der Oberklasse direkt aufzurufen: rufen Sie einfach "BaseClassName.methodname(self, arguments)" auf. Dies ist gelegentlich auch für Klienten nützlich. (Man beachte, daß dies nur funktioniert, wenn die Oberklasse direkt im globalen Geltungsbereich definiert oder importiert ist.)


9.5.1 Mehrfach-Vererbung

Python unterstützt auch eine beschränkte Form von Mehrfach-Vererbung. Eine Klassen-Definition mit mehreren Oberklassen sieht wie folgt aus:


class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

Die einzige Regel, die man braucht, um die Semantik zu erklären, ist die Auflösungs-Regel für Referenzen von Klassenattributen. Diese vollzieht sich in einer Tiefensuche und von links nach rechts. Wenn also ein Attribut in DerivedClassName nicht gefunden wird, wird es in Base1, dann (rekursiv) in den Oberklassen von Base1, und nur wenn es dort nicht gefunden wird, wird es in Base2 gesucht, u.s.w.

(Einigen Leuten erscheint Breitensuche - die Suche in Base2 und Base3 vor den Oberklassen von Base1 - natürlicher. Dazu müßte man jedoch wissen, ob ein spezielles Attribut von Base1 tatsächlich in Base1 definiert ist oder in einer seiner Oberklassen, bevor man die Konsequenzen eines Namenskonfliktes mit einem Attribut von Base2 absehen kann. Tiefensuche macht keinen Unterschied zwischen direkten und von Base1 ererbten Attributen.)

Es ist klar, daß undifferenzierte Verwendung von Mehrfach-Vererbung ein Alptraum in Sachen Wartung bedeutet, da Python sich auf Konventionen verläßt, um versehentliche Namenskonflikte zu vermeiden. Ein bekanntes Problem mit Mehrfach-Vererbung ist eine Klasse, die von zwei Oberklassen abgeleitet ist, die eine gemeinsame Oberklasse haben. Während es recht einfach ist, sich auszumalen, was in diesem Fall passiert (die Instanz wird eine einzige Kopie von ,,Instanzvariablen`` oder Datenattributen haben, die von der gemeinsamen Oberklasse stammen), ist es nicht klar, ob diese Semantik in irgendeiner Art sinnvoll ist.


9.6 Private Variablen

Es gibt nur eine eingeschränkte Unterstützung von privaten Klassenbezeichnern. Jeder Bezeichner der Form __spam (mindestens zwei führende Unterstriche, höchstens ein abschließendes) wird textuell mit _classname__spam ersetzt, wobei classname der aktuelle Klassenname ohne führende Unterstriche ist. Diese Namensverstümmelung (engl. name mangling) wird ohne Berücksichtigung der syntaktisches Position des Bezeichners durchgeführt, so daß es verwendet werden kann, um private Klassen- und Instanzvariablen zu definieren, wie auch Methoden und globale Variablen und sogar, um private Instanzvariablen dieser Klasse in Instanzen anderer Klassen zu speichern. Der so gebildete Name kann abgeschnitten werden, wenn er mehr als 255 Zeichen lang ist. Außerhalb von Klassen oder wenn der Klassenname nur aus Unterstrichen besteht, findet keine Verstümmelung statt.

Diese Verstümmelung beabsichtigt, Klassen eine einfache Möglichkeit zu bieten, ,,private`` Instanzvariablen und -methoden zu definieren, ohne sich um von anderen Klassen definierte Instanzvariablen sorgen zu müssen oder sich mit Instanzvariablen in Code außerhalb der Klasse beschäftigen zu müssen. Man beachte, daß die Verstümmelungs-Regeln entworfen wurden, um Unfälle zu vermeiden. Es ist jedoch für einen entschlossenen Menschen immer noch möglich, sich Zugang zu einer Variablen zu verschaffen, die als privat gilt, und diese zu ändern. Das kann sogar nützlich sein, z.B. für den Debugger, und das ist ein Grund, warum dieses Loch nicht geschlossen wird. (Eine kleine Unzulänglichkeit am Rande: die Ableitung einer Klasse mit dem gleichen Namen wie dem der Oberklasse gestattet die Verwendung der privaten Variablen der Oberklasse.)

Man beachte ferner, daß Code, der an exec(), eval() oder evalfile() übergeben wird, den Klassennamen der aufrufenden Klasse nicht als aktuelle Klasse betrachtet; das ist ähnlich wie bei der global-Anweisung, deren Effekt genauso auf Code beschränkt ist, der zusammen Byte-übersetzt wurde. Dieselbe Einschränkung gilt für getattr(), setattr() und delattr() wie auch bei der direkten Referenzierung von __dict__.

Hier ist ein Beispiel einer Klasse, die ihre eigenen __getattr__- und __setattr__-Methoden implementiert und alle Attribute in einer privaten Variable abspeichert. In gewisser Weise funktioniert dies in Python 1.4 genauso wie in früheren Versionen:


class VirtualAttributes:
    __vdict = None
    __vdict_name = locals().keys()[0]

    def __init__(self):
        self.__dict__[self.__vdict_name] = {}

    def __getattr__(self, name):
        return self.__vdict[name]

    def __setattr__(self, name, value):
        self.__vdict[name] = value


9.7 Diverses

Manchmal ist es hilfreich, einen Datentyp zu benutzen, der ähnlich zu ,,record`` in Pascal oder ,,struct`` in C ist und eine Anzahl mit Namen versehener Daten gruppiert. Eine leere Klassen-Definition erledigt das auf elegante Weise, z.B.:


class Employee:
    pass

john = Employee() # Erzeuge einen leeren Mitarbeiter-Satz.

# Fuelle die Felder des Satzes.
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

Einem Stück Python-Code, der einen speziellen abstrakten Datentyp erwartet, kann man oftmals eine Klasse übergeben, die die Methoden dieses Datentyps emuliert. Wenn Sie z.B. eine Funktion haben, die irgendwelche Daten eines Datei-Objektes formatiert, können Sie eine Klasse mit den Methoden read() und readline() definieren, die Daten von einem String-Puffer erhält, und diese als Argument übergeben.

Instanzmethoden-Objekte haben auch Attribute: m.im_self ist das Objekt, dessen Instanz diese Methode ist, und m.im_func ist das Funktions-Objekt, die der Methode entspricht.


9.7.1 Ausnahmen können Klassen sein

Benutzerdefinierte Ausnahmen sind nicht mehr auf String-Objekte eingeschränkt - es können auch Klassen sein. Mit diesem Mechanismus ist es möglich, erweiterbare Ausnahme-Hierarchien zu erzeugen.

Es gibt zwei neue gültige (semantische) Formen für die raise-Anweisung:


raise Class, instance

raise instance

In der ersten Form muß instance eine Instanz von Class oder einer davon abgeleiteten Klasse sein. Die zweite Form ist eine Abkürzung für


raise instance.__class__, instance

Eine except-Klausel darf Klassen wie auch String-Objekte auflisten. Eine Klasse in einer except-Klausel ist kompatibel mit einer Ausnahme, wenn es dieselbe Klasse oder eine Oberklasse davon ist (aber nicht umgekehrt - eine except-Klausel, die eine abgeleitete Klasse auflistet, ist nicht kompatibel mit einer Oberklasse). Der folgende Code z.B. wird B, C, D in dieser Reihenfolge ausgeben:


class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B, C, D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B"

Man beachte, daß, falls die except-Klauseln umgekehrt werden (zuerst "except B"), B, B, B ausgegeben worden wäre - die erste passende except-Klausel wird ausgelöst.

Wenn eine Fehlermeldung für eine unbehandelte Ausnahme ausgegeben wird, die eine Klasse ist, wird der Klassenname ausgegeben, dann ein Doppelpunkt und ein Leerzeichen und schließlich die Instanz, nachdem sie mit der eingebauten Funktion str() konvertiert wurde.



Fußnoten

... Namensraum!9.1
Außer in einem: Modul-Objekte haben ein geheimes nur-lesbares Attribut namens __dict__, das das Dictionary zurück gibt, mit dem der Namensraum des Moduls implementiert ist. Der Name __dict__ ist ein Attribut, aber kein globaler Name. Offensichtlich verletzt seine Verwendung die Abstraktion der Implementierung von Namensräumen und sollte auf Programme wie post-mortem-Debugger beschränkt werden.


Send comments to python-docs@python.org.