9. Klassen
**********

Klassen bieten die Möglichkeit, Daten und Funktionalitäten
zusammenzufassen. Das Erstellen einer neuen Klasse erzeugt einen neuen
*Typ* von Objekt, was es ermöglicht, neue *Instanzen* dieses Typs zu
erstellen. Jede Klasseninstanz kann über Attribute verfügen, um ihren
Zustand zu verwalten. Klasseninstanzen können auch über Methoden
(definiert durch ihre Klasse) verfügen, um ihren Zustand zu ändern.

Im Vergleich zu anderen Programmiersprachen fügt der
Klassenmechanismus von Python Klassen mit einem Minimum an neuer
Syntax und Semantik hinzu. Er ist eine Mischung aus den
Klassenmechanismen von C++ und Modula-3. Python-Klassen bieten alle
Standardfunktionen der objektorientierten Programmierung: Der
Vererbungungsmechanismus erlaubt mehrere Basisklassen, eine
abgeleitete Klasse kann jede Methode ihrer Basisklasse(n)
überschreiben, und eine Methode kann die gleichnamige Methode einer
Basisklasse aufrufen. Objekte können beliebige Mengen und Arten von
Daten enthalten. Wie auch Module zeichnen sich Klassen durch die
dynamische Natur von Python aus: Sie werden zur Laufzeit erzeugt und
können nach ihrer Erstellung weiter verändert werden.

In der C++-Terminologie sind Klassenmitglieder (einschließlich der
Datenelemente) normalerweise *public* (Ausnahmen siehe unten Private
Variablen), und alle Elementfunktionen sind *virtual*. Wie in Modula-3
gibt es keine Kurzschreibweisen für den Zugriff auf die Elemente eines
Objekts aus dessen Methoden: Die Methodenfunktion wird mit einem
expliziten ersten Argument deklariert, das das Objekt darstellt und
beim Aufruf implizit übergeben wird. Wie in Smalltalk sind Klassen
selbst Objekte. Dies bietet eine Semantik zum Importieren und
Umbenennen. Anders als in C++ und Modula-3 können eingebaute Typen als
Basisklassen für Erweiterungen durch den Benutzer verwendet werden.
Zudem können, wie in C++, die meisten eingebauten Operatoren mit
spezieller Syntax (arithmetische Operatoren, Indizierung usw.) für
Klasseninstanzen neu definiert werden.

(Da eine allgemein akzeptierte Terminologie für Klassen fehlt, werde
ich gelegentlich Begriffe aus Smalltalk und C++ verwenden. Ich würde
Begriffe aus Modula-3 verwenden, da dessen objektorientierte Semantik
der von Python näher kommt als die von C++, aber ich nehme an, dass
nur wenige Leser davon gehört haben.)


9.1. Ein Wort zu Namen und Objekten
===================================

Objekte besitzen eine eigene Identität, und mehrere Namen (in
verschiedenen Gültigkeitsbereichen) können an dasselbe Objekt gebunden
sein. Dies ist in anderen Sprachen als Aliasing bekannt. Auf den
ersten Blick wird dies bei Python meist nicht wahrgenommen und kann
beim Umgang mit unveränderlichen Datentypen (Zahlen, Zeichenketten,
Tupel) getrost ignoriert werden. Aliasing hat jedoch eine unter
Umständen überraschende Wirkung auf die Semantik von Python-Code, der
veränderliche Objekte wie Listen, Dictionaries und die meisten anderen
Typen enthält. Dies wird gewöhnlich zum Vorteil des Programms genutzt,
da sich Aliase in mancher Hinsicht wie Zeiger verhalten.
Beispielsweise ist die Übergabe eines Objekts günstig, da von der
Implementierung nur ein Zeiger übergeben wird; und wenn eine Funktion
ein als Argument übergebenes Objekt verändert, sieht der Aufrufer
diese Änderung --- was zwei verschiedene Mechanismen zur
Argumentübergabe wie in Pascal überflüssig macht.


9.2. Python-Gültigkeitsbereiche und Namensräume
===============================================

Bevor wir Klassen einführen, muss ich dir zuerst etwas über Pythons
Gültigkeitsbereiche (Scopes) erzählen. Klassendefinitionen wenden
einige Raffinessen bei Namensräumen an, und du musst wissen, wie
Gültigkeitsbereiche und Namensräume funktionieren, um vollständig zu
verstehen, was passiert. Nebenbei bemerkt ist das Wissen über dieses
Thema für jeden fortgeschrittenen Python-Programmierer nützlich.

Beginnen wir mit einigen Definitionen.

Ein *Namensraum* ist eine Zuordnung von Namen zu Objekten. Die meisten
Namensräume sind derzeit als Python-Dictionaries implementiert, was
aber normalerweise in keiner Weise auffällt (außer bei der
Performance) und sich in Zukunft ändern kann. Beispiele für
Namensräume sind: die Menge der eingebauten Namen (die Funktionen wie
"abs()" und eingebaute Ausnahmen enthält), die globalen Namen in einem
Modul und die lokalen Namen bei einem Funktionsaufruf. In gewissem
Sinne bildet auch die Menge der Attribute eines Objekts einen
Namensraum. Das Wichtigste über Namensräume ist, dass es absolut keine
Beziehung zwischen Namen in verschiedenen Namensräumen gibt, so können
beispielsweise zwei verschiedene Module eine Funktion "maximize" ohne
Verwirrung definieren --- Benutzer der Module müssen ihr den
Modulnamen voranstellen.

Übrigens verwende ich das Wort *Attribut* für jeden Namen, der einem
Punkt folgt --- im Ausdruck "z.real" ist beispielsweise "real" ein
Attribut des Objekts "z". Strenge genommen sind Referenzen auf Namen
in Modulen Attributreferenzen: Im Ausdruck "modname.funcname" ist
"modname" ein Modulobjekt und "funcname" ein Attribut davon. In diesem
Fall gibt es zufällig eine direkte Zuordnung zwischen den Attributen
des Moduls und den im Modul definierten globalen Namen: Sie teilen
sich denselben Namensraum! [1]

Attribute können schreibgeschützt oder beschreibbar sein. Im letzteren
Fall ist eine Zuweisung an Attribute möglich. Modulattribute sind
beschreibbar: du kannst "modname.the_answer = 42" schreiben.
Beschreibbare Attribute "können auch mit der Anweisung "del" gelöscht
werden. Zum Beispiel entfernt "del modname.the_answer" das Attribut
"the_answer" aus dem Objekt namens "modname".

Namensräume werden zu unterschiedlichen Zeitpunkten erstellt und haben
unterschiedliche Lebensdauern. Der Namensraum, der die eingebauten
Namen enthält, wird beim Start des Python-Interpreters erstellt und
niemals gelöscht. Der globale Namensraum für ein Modul wird beim
Einlesen der Moduldefinition erstellt, normalerweise bleiben
Modulnamensräume ebenfalls bestehen, bis der Interpreter beendet wird.
Die Anweisungen, die auf oberster Ebene des Interpreters ausgeführt
werden, sei es aus einer Skriptdatei oder interaktiv, gelten als Teil
eines Moduls namens "__main__" und haben daher ihren eigenen globalen
Namensraum. (Die eingebauten Namen befinden sich eigentlich ebenfalls
in einem Modul; dieses heißt "builtins".)

Der lokale Namensraum für eine Funktion wird beim Aufruf der Funktion
erstellt und gelöscht, wenn die Funktion zurückkehrt oder eine
Ausnahme auslöst, die nicht innerhalb der Funktion behandelt wird.
(Eigentlich wäre Vergessen eine bessere Beschreibung für das, was
tatsächlich passiert.)  Selbstverständlich haben rekursive Aufrufe
jeweils ihren eigenen lokalen Namensraum.

Ein *Gültigkeitsbereich* (Scope) ist ein Quelltextbereich eines
Python-Programms, in dem ein Namensraum direkt zugänglich ist. "Direkt
zugänglich" bedeutet hier, dass eine unqualifizierte Referenz auf
einen Namen versucht, den Namen in diesem Namensraum zu finden.

Obwohl Gültigkeitsbereiche statisch bestimmt werden, werden sie
dynamisch verwendet. Zu jedem Zeitpunkt der Ausführung gibt es 3 oder
4 verschachtelte Gültigkeitsbereiche, deren Namensräume direkt
zugänglich sind:

* der innerste Gültigkeitsbereich, der zuerst durchsucht wird, enthält
  die lokalen Namen

* die Gültigkeitsbereiche etwaiger umgebender Funktionen, die
  ausgehend vom nächstgelegenen umgebenden Bereich durchsucht werden,
  enthalten nicht-lokale, aber auch nicht-globale Namen

* der vorletzte Gültigkeitsbereich enthält die globalen Namen des
  aktuellen Moduls

* der äußerste Gültigkeitsbereich (der zuletzt durchsucht wird) ist
  der Namensraum, der die eingebauten Namen enthält

Wird ein Name als global deklariert, gehen alle Referenzen und
Zuweisungen direkt an den vorletzten Gültigkeitsbereich, der die
globalen Namen des Moduls enthält. Um Variablen neuzuweisen, die sich
außerhalb des innersten Gültigkeitsbereichs befinden, kann die
Anweisung "nonlocal" verwendet werden, sind sie nicht als nonlocal
deklariert, sind diese Variablen schreibgeschützt (ein Versuch, in
eine solche Variable zu schreiben, erzeugt lediglich eine *neue*
lokale Variable im innersten Gültigkeitsbereich und lässt die
gleichnamige äußere Variable unverändert).

Normally verweist der lokale Gültigkeitsbereich auf die lokalen Namen
der (im Quelltext) aktuellen Funktion. Außerhalb von Funktionen
verweist der lokale Gültigkeitsbereich auf denselben Namensraum wie
der globale Gültigkeitsbereich: den Namensraum des Moduls.
Klassendefinitionen platzieren einen weiteren Namensraum im lokalen
Gültigkeitsbereich.

Es ist wichtig zu verstehen, dass Gültigkeitsbereiche quelltextbezogen
bestimmt werden: Der globale Gültigkeitsbereich einer in einem Modul
definierten Funktion ist der Namensraum dieses Moduls, ganz gleich,
von wo oder unter welchem Alias die Funktion aufgerufen wird.
Andererseits erfolgt die tatsächliche Suche nach Namen dynamisch zur
Laufzeit --- die Sprachdefinition entwickelt sich jedoch in Richtung
einer statischen Namensauflösung zur "Kompilierzeit", verlasse dich
also nicht auf die dynamische Namensauflösung! (Tatsächlich werden
lokale Variablen bereits statisch bestimmt.)

Eine Besonderheit von Python ist, dass Zuweisungen an Namen – wenn
keine "global"- oder "nonlocal"-Anweisung gilt – immer in den
innersten Gültigkeitsbereich eingehen. Zuweisungen kopieren keine
Daten --- sie binden lediglich Namen an Objekte. Das Gleiche gilt für
Löschungen: Die Anweisung "del x" entfernt die Bindung von "x" aus dem
Namensraum, auf den der lokale Gültigkeitsbereich verweist.
Tatsächlich nutzen alle Operationen, die neue Namen einführen, den
lokalen Gültigkeitsbereich: Insbesondere binden "import"-Anweisungen
und Funktionsdefinitionen den Modul- oder Funktionsnamen im lokalen
Gültigkeitsbereich.

Die Anweisung "global" kann verwendet werden, um anzugeben, dass
bestimmte Variablen im globalen Gültigkeitsbereich liegen und dort neu
gebunden werden sollen, die Anweisung "nonlocal" gibt an, dass
bestimmte Variablen in einem umgebenden Gültigkeitsbereich liegen und
dort neu gebunden werden sollen.


9.2.1. Beispiel für Gültigkeitsbereiche und Namensräume
-------------------------------------------------------

Dies ist ein Beispiel, das zeigt, wie auf die verschiedenen
Gültigkeitsbereiche und Namensräume zugegriffen wird und wie "global"
und "nonlocal" die Variablenbindung beeinflussen:

   def scope_test():
       def do_local():
           spam = "local spam"

       def do_nonlocal():
           nonlocal spam
           spam = "nonlocal spam"

       def do_global():
           global spam"
           spam = "global spam"

       spam = "test spam"
       do_local()
       print("After local assignment:", spam)
       do_nonlocal()
       print("After nonlocal assignment:", spam)
       do_global()
       print("After global assignment:", spam)

   scope_test()
   print("In global scope:", spam)

Die Ausgabe des Beispielcodes lautet:

   After local assignment: test spam
   After nonlocal assignment: nonlocal spam
   After global assignment: nonlocal spam
   In global scope: global spam

Beachte, wie die *lokale* Zuweisung (die der Standard ist) die Bindung
von *spam* in *scope_test* nicht verändert hat. Die
"nonlocal"-Zuweisung hat die Bindung von *spam* in *scope_test*
geändert und die "global"-Zuweisung hat die Bindung auf Modulebene
geändert.

Du kannst auch sehen, dass vor der "global"-Zuweisung keine vorherige
Bindung für *spam* existierte.


9.3. Ein erster Blick auf Klassen
=================================

Klassen führen ein wenig neue Syntax, drei neue Objekttypen und einige
neue Semantiken ein.


9.3.1. Syntax von Klassendefinitionen
-------------------------------------

Die einfachste Form einer Klassendefinition sieht so aus:

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

Klassendefinitionen müssen, genau wie Funktionsdefinitionen
("def"-Anweisungen), ausgeführt werden, bevor sie eine Wirkung haben.
(Man könnte eine Klassendefinition denkbarer Weise in einem Zweig
einer "if"-Anweisung oder innerhalb einer Funktion platzieren.)

In der Praxis sind die Anweisungen innerhalb einer Klassendefinition
üblicherweise  Funktionsdefinitionen, aber andere Anweisungen sind
zulässig und manchmal nützlich --- darauf kommen wir später zurück.
Die Funktionsdefinitionen innerhalb einer Klasse haben normalerweise
eine besondere Form von Argumentliste, die durch die
Aufrufkonventionen für Methoden vorgegeben ist --- auch dies wird
später erklärt.

Wird eine Klassendefinition betreten, wird ein neuer Namensraum
erzeugt und als lokaler Gültigkeitsbereich verwendet --- somit gehen
alle Zuweisungen an lokale Variablen in diesen neuen Namensraum.
Insbesondere binden Funktionsdefinitionen hier den Namen der neuen
Funktion.

Wird eine Klassendefinition normal verlassen (durch das Ende), wird
ein *Klassenobjekt* erzeugt. Dies ist im Wesentlichen eine Hülle um
den Inhalt des durch die Klassendefinition erstellten Namensraums; wir
werden im nächsten Abschnitt mehr über Klassenobjekte lernen. Der
ursprüngliche lokale Gültigkeitsbereich (derjenige, der direkt vor dem
Betreten der Klassendefinition galt) wird wiederhergestellt und das
Klassenobjekt wird hier an den im Kopf der Klassendefinition
angegebenen Klassennamen ("ClassName" im Beispiel) gebunden.


9.3.2. Klassenobjekte
---------------------

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

*Attributreferenzen* verwenden die Standardsyntax, die für alle
Attributreferenzen in Python genutzt wird: "obj.name". Gültige
Attributnamen sind alle Namen, die sich im Namensraum der Klasse
befanden, als das Klassenobjekt erzeugt wurde. Wenn die
Klassendefinition also so aussah:

   class MyClass:
       """A simple example class"""
       i = 12345

       def f(self):
           return 'hello world'

dann sind "MyClass.i" und "MyClass.f" gültige Attributreferenzen, die
jeweils eine Ganzzahl beziehungsweise ein Funktionsobjekt zurückgeben.
Klassenattribute können auch zugewiesen werden, sodass man den Wert
von "MyClass.i" durch eine Zuweisung ändern kann. "__doc__" ist
ebenfalls ein gültiges Attribut, das den zur Klasse gehörenden
Docstring zurückgibt: "\"A simple example class\"".

Die *Instanziierung* einer Klasse verwendet Funktionsnotation. Stell
dir einfach vor, dass das Klassenobjekt eine parameterlose Funktion
ist, die eine neue Instanz der Klasse zurückgibt. Zum Beispiel (unter
Annahme der obigen Klasse):

   x = MyClass()

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

Die Instanziierungsoperation (das "Aufrufen" eines Klassenobjekts)
erzeugt ein leeres Objekt. Viele Klassen erzeugen Objekte bevorzugt
mit Instanzen, die auf einen bestimmten Anfangszustand angepasst sind.
Daher kann eine Klasse eine spezielle Methode namens "__init__()"
definieren, wie etwa so:

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

Wenn eine Klasse eine "__init__()"-Methode definiert, ruft die
Klasseninstanziierung automatisch "__init__()" für die neu erzeugte
Klasseninstanz auf. In diesem Beispiel kann eine neue, initialisierte
Instanz also wie folgt erzeugt werden:

   x = MyClass()

Natürlich kann die "__init__()"-Methode für mehr Flexibilität auch
Argumente besitzen. In diesem Fall werden dem
Klasseninstanziierungsoperator übergabene Argumente an "__init__()"
weitergeleitet. Zum 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)


9.3.3. Instanzobjekte
---------------------

Was können wir nun mit Instanzobjekten tun? Die einzigen Operationen,
die von Instanzobjekten verstanden werden, sind Attributreferenzen. Es
gibt zwei Arten von gültigen Attributnamen: Datenattribute und
Methoden.

*data attributes* correspond to "instance variables" in Smalltalk, and
to "data members" in C++.  Data attributes need not be declared; like
local variables, they spring into existence when they are first
assigned to.  For example, if "x" is the instance of "MyClass" created
above, the following piece of code will print the value "16", without
leaving a trace:

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

Die andere Art von Instanzattributreferenz ist eine *Methode*. Eine
Methode ist eine Funktion, die zu einem Objekt „gehört“.

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


9.3.4. Methodenobjekte
----------------------

Normalerweise wird eine Methode direkt aufgerufen, nachdem sie
gebunden wurde:

   x.f()

Wenn "x = MyClass()" gilt (wie oben), gibt dies die Zeichenkette
"'hello world'" zurück. Es ist jedoch nicht notwendig, eine Methode
sofort aufzurufen: "x.f" ist ein Methodenobjekt und kann gespeichert
und zu einem späteren Zeitpunkt aufgerufen werden. Zum Beispiel:

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

gibt bis ans Ende der Zeit weiterhin "hello world" aus.

Was genau passiert, wenn eine Methode aufgerufen wird? Du hast
vielleicht bemerkt, dass "x.f()" oben ohne Argument aufgerufen wurde,
obwohl die Funktionsdefinition für "f()" ein Argument angab. Was ist
mit dem Argument passiert? Python löst sicherlich eine Ausnahme aus,
wenn eine Funktion, die ein Argument erfordert, ohne ein solches
aufgerufen wird --- selbst wenn das Argument gar nicht verwendet
wird...

Eigentlich hast du die Antwort vielleicht schon geahnt: Das Besondere
an Methoden ist, dass das Instanzobjekt als erstes Argument der
Funktion übergeben wird. In unserem Beispiel ist der Aufruf "x.f()"
genau äquivalent zu "MyClass.f(x)". Im Allgemeinen entspricht der
Aufruf einer Methode mit einer Liste von *n* Argumenten dem Aufruf der
entsprechenden Funktion mit einer Argumentliste, die durch Einfügen
des Instanzobjekts der Methode vor dem ersten Argument erstellt wird.

Im Allgemeinen funktionieren Methoden wie folgt: Wenn auf ein Attribut
einer Instanz verwiesen wird, das kein Datenattribut ist, wird die
Klasse der Instanz durchsucht. Bezeichnet der Name ein gültiges
Klassenattribut, das ein Funktionsobjekt ist, werden Referenzen auf
das Instanzobjekt und das Funktionsobjekt in ein Methodenobjekt
gepackt. Wird das Methodenobjekt mit einer Argumentliste aufgerufen,
wird aus dem Instanzobjekt und der Argumentliste eine neue
Argumentliste erstellt und das Funktionsobjekt mit dieser neuen
Argumentliste aufgerufen.


9.3.5. Klassen- und Instanzvariablen
------------------------------------

Allgemein gesagt sind Instanzvariablen für Daten gedacht, die für jede
Instanz eindeutig sind, während Klassenvariablen für Attribute und
Methoden gedacht sind, die von allen Instanzen der Klasse geteilt
werden:

   class Dog:

       kind = 'canine'         # class variable shared by all instances

       def __init__(self, name):
           self.name = name    # instance variable unique to each instance

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.kind                  # shared by all dogs
   'canine'
   >>> e.kind                  # shared by all dogs
   'canine'
   >>> d.name                  # unique to d
   'Fido'
   >>> e.name                  # unique to e
   'Buddy'

Wie in Ein Wort zu Namen und Objekten besprochen, können geteilte
Daten in Verbindung mit *veränderbaren* Objekten wie Listen und
Wörterbüchern überraschende Effekte haben. Die Liste *tricks* im
folgenden Code sollte beispielsweise nicht als Klassenvariable
verwendet werden, da nur eine einzige Liste von allen *Dog*-Instanzen
geteilt würde:

   class Dog:

       tricks = []             # falsche Verwendung einer Klassenvariable

       def __init__(self, name):
           self.name = name

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

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.add_trick('roll over')
   >>> e.add_trick('play dead')
   >>> d.tricks                # unerwartet von allen Hunden geteilt
   ['roll over', 'play dead']

Ein korrektes Design der Klasse sollte stattdessen eine
Instanzvariable verwenden:

   class Dog:

       def __init__(self, name):
           self.name = name
           self.tricks = []    # erzeugt eine neue leere Liste für jeden Hund

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

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.add_trick('roll over')
   >>> e.add_trick('play dead')
   >>> d.tricks
   ['roll over']
   >>> e.tricks
   ['play dead']


9.4. Ergänzende Hinweise
========================

Tritt derselbe Attributname sowohl in einer Instanz als auch in einer
Klasse auf, bevorzugt die Attributsuche die Instanz:

   >>> class Warehouse:
   ...    purpose = 'storage'
   ...    region = 'west'
   ...
   >>> w1 = Warehouse()
   >>> print(w1.purpose, w1.region)
   storage west
   >>> w2 = Warehouse()
   >>> w2.region = 'east'
   >>> print(w2.purpose, w2.region)
   storage east

Datenattribute können sowohl von Methoden als auch von gewöhnlichen
Benutzern („Clients“) eines Objekts referenziert werden. Mit anderen
Worten: Klassen sind nicht dazu geeignet, reine abstrakte Datentypen
zu implementieren. In Python gibt es tatsächlich nichts, was eine
Kapselung (Data Hiding) erzwingen könnte --- alles beruht auf
Konventionen. (Andererseits kann die in C geschriebene Python-
Implementierung Implementierungsdetails vollständig verbergen und den
Zugriff auf ein Objekt bei Bedarf steuern; dies kann von in C
geschriebenen Python-Erweiterungen genutzt werden.)

Benutzer sollten Datenattribute mit Vorsicht verwenden --- sie können
von Methoden aufrechterhaltene Invarianten beschädigen, indem sie
deren Datenattribute überschreiben. Beachte, dass Benutzer einem
Instanzobjekt eigene Datenattribute hinzufügen können, ohne die
Gültigkeit der Methoden zu beeinträchtigen, solange Namenskonflikte
vermieden werden --- auch hier kann eine Namenskonvention viel Ärger
ersparen.

Es gibt keine Kurzschreibweise, um innerhalb von Methoden auf
Datenattribute (oder andere Methoden!) zu verweisen. Dies erhöht
tatsächlich die Lesbarkeit von Methoden: Beim Durchsehen einer Methode
besteht keine Gefahr, lokale Variablen und Instanzvariablen 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 spezielle Bedeutung. Beachte jedoch, dass dein Code für
andere Python-Programmierer schwerer lesbar sein kann, wenn du dich
nicht an diese Konvention hältst, und es ist auch denkbar, dass ein
*Class-Browser*-Programm geschrieben wird, das sich auf eine solche
Konvention verlässt.

Jedes Funktionsobjekt, das ein Klassenattribut ist, definiert eine
Methode für Instanzen dieser Klasse. Es ist nicht erforderlich, dass
die Funktionsdefinition textuell in die Klassendefinition
eingeschlossen ist: Das Zuweisen eines Funktionsobjekts zu einer
lokalen Variable in der Klasse ist ebenfalls in Ordnung. Zum Beispiel:

   # Außerhalb der Klasse definierte Funktions
   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 auf
Funktionsobjekte verweisen, und folglich sind sie alle Methoden von
Instanzen von "C" --- wobei "h" genau äquivalent zu "g" ist. Beachte,
dass diese Praxis gewöhnlich nur dazu dient, den Leser eines Programms
zu verwirren.

Methoden können andere Methoden aufrufen, indem sie Methodenattribute
des " ""self"-Arguments verwenden:

   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 auf globale Namen auf dieselbe Weise verweisen wie
gewöhnliche Funktionen. Der mit einer Methode verknüpfte globale
Gültigkeitsbereich ist das Modul, das ihre Definition enthält. (Eine
Klasse wird nie als globaler Gültigkeitsbereich verwendet.) Während
man selten einen guten Grund dafür findet, globale Daten in einer
Methode zu verwenden, gibt es viele berechtigte Anwendungen für den
globalen Gültigkeitsbereich: Zum einen können in den globalen
Gültigkeitsbereich importierte Funktionen und Module von Methoden
verwendet werden, ebenso wie darin definierte Funktionen und Klassen.
Üblicherweise ist die Klasse, die die Methode enthält, selbst in
diesem globalen Gültigkeitsbereich definiert, und im nächsten
Abschnitt werden wir einige gute Gründe dafür finden, warum eine
Methode auf ihre eigene Klasse verweisen möchte.

Jeder Wert ist ein Objekt und hat daher eine *Klasse* (auch *Typ*
genannt). Diese ist als "object.__class__" gespeichert.


9.5. Vererbung
==============

Natürlich wäre ein Sprachfeature des Namens „Klasse“ nicht würdig,
wenn es keine Vererbung unterstützen würde. Die Syntax für die
Definition einer abgeleiteten Klasse sieht wie folgt aus:

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

Der Name "BaseClassName" muss in einem Namensraum definiert sein, der
aus dem Gültigkeitsbereich der abgeleiteten Klassendefinition heraus
zugänglich ist. Anstelle eines Basisklassennamens sind auch andere
beliebige Ausdrücke erlaubt. Dies kann beispielsweise nützlich sein,
wenn die Basisklasse in einem anderen Modul definiert ist:

   class DerivedClassName(modname.BaseClassName):

Die Ausführung einer abgeleiteten Klassendefinition verläuft genauso
wie für eine Basisklasse. Wenn das Klassenobjekt erzeugt wird, wird
die Basisklasse gemerkt. Dies wird zur Auflösung von
Attributreferenzen verwendet: Wird ein angefordertes Attribut nicht in
der Klasse gefunden, wird in der Basisklasse weitergesucht. Diese
Regel wird rekursiv angewendet, wenn die Basisklasse selbst von einer
anderen Klasse abgeleitet ist.

Die Instanziierung abgeleiteter Klassen weist keine Besonderheiten
auf: "DerivedClassName()" erzeugt eine neue Instanz der Klasse.
Methodenreferenzen werden wie folgt aufgelöst: Das entsprechende
Klassenattribut wird gesucht, wobei bei Bedarf die Kette der
Basisklassen durchsucht wird. Die Methodenreferenz ist gültig, wenn
dies ein Funktionsobjekt ergibt.

Abgeleitete Klassen können Methoden ihrer Basisklassen überschreiben.
Da Methoden keine besonderen Privilegien beim Aufruf anderer Methoden
desselben Objekts besitzen, kann das Aufrufen einer in derselben
Basisklasse definierten Methode letztlich dazu führen, dass eine
Methode einer abgeleiteten Klasse aufgerufen wird, die diese
überschreibt. (Für C++ Programmierer: Alle Methoden in Python sind
effektiv "virtual".)

Eine überschreibende Methode in einer abgeleiteten Klasse möchte die
Methode gleichen Namens der Basisklasse womöglich erweitern, anstatt
sie einfach zu ersetzen. Es gibt einen einfachen Weg, die Methode der
Basisklasse direkt aufzurufen: Man ruft einfach
"BaseClassName.methodname(self, argumente)" auf. Dies ist gelegentlich
auch für Clients nützlich. (Beachte, dass dies nur funktioniert, wenn
die Basisklasse als "BaseClassName" im globalen Gültigkeitsbereich
zugänglich ist.)

Python bietet zwei eingebaute Funktionen, die mit Vererbung arbeiten:

* Verwende "isinstance()", um den Typ einer Instanz zu überprüfen:
  "isinstance(obj, int)" ist nur dann "True", wenn "obj.__class__"
  gleich "int" oder einer von "int" abgeleiteten Klasse ist.

* Verwende "issubclass()", um die Klassenvererbung zu überprüfen:
  "issubclass(bool, int)" ist "True", da "bool" eine Unterklasse von
  "int" ist. "issubclass(float, int)" ist jedoch "False", da "float"
  keine Unterklasse von "int" ist.


9.5.1. Mehrfachvererbung
------------------------

Python unterstützt auch eine Form der Mehrfachvererbung. Eine
Klassendefinition mit mehreren Basisklassen sieht wie folgt aus:

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

Für die meisten Zwecke kann man sich die Suche nach Attributen, die
von einer Elternklasse geerbt wurden, in den einfachsten Fällen als
Tiefensuche von links nach rechts vorstellen, bei der dieselbe Klasse
bei Überschneidungen in der Hierarchie nicht zweimal durchsucht wird.
Wenn ein Attribut also nicht in "DerivedClassName" gefunden wird, wird
in "Base1" gesucht, dann (rekursiv) in den Basisklassen von "Base1",
und falls es dort nicht gefunden wurde, wird in "Base2" gesucht und so
weiter.

Tatsächlich ist es etwas komplexer: Die Method Auflösungsreihenfolge
(Method Resolution Order) ändert sich dynamisch, um kooperative
Aufrufe von "super()" zu unterstützen. Dieser Ansatz ist in einigen
anderen Sprachen mit Mehrfachvererbung als „call-next-method“ bekannt
und ist mächtiger als der Super-Aufruf in Sprachen mit einfacher
Vererbung.

Eine dynamische Reihenfolge ist erforderlich, da alle Fälle von
Mehrfachvererbung eine oder mehrere Diamant-Beziehungen aufweisen (bei
denen auf mindestens eine der Elternklassen über mehrere Pfade von der
untersten Klasse aus zugegriffen werden kann). So erben beispielsweise
alle Klassen von "object", sodass jeder Fall von Mehrfachvererbung
mehr als einen Pfad bietet, um "object" zu erreichen. Um zu
verhindern, dass auf die Basisklassen mehr als einmal zugegriffen
wird, linearisiert der dynamische Algorithmus die Suchreihenfolge so,
dass die in jeder Klasse angegebene Reihenfolge von links nach rechts
erhalten bleibt, jede Elternklasse nur einmal aufgerufen wird und die
Reihenfolge monoton ist (was bedeutet, dass von einer Klasse
abgeleitet werden kann, ohne die Vorrangstellung ihrer Elternklassen
zu beeinträchtigen). Zusammengefasst ermöglichen diese Eigenschaften
den Entwurf zuverlässiger und erweiterbarer Klassen mit
Mehrfachvererbung. Weitere Details finden Sie unter The Python 2.3
Method Resolution Order.


9.6. Private Variablen
======================

Private“ Instanzvariablen, auf die nur von innerhalb eines Objekts
zugegriffen werden kann, gibt es in Python nicht. Es gibt jedoch eine
Konvention, an die sich der meiste Python-Code hält: Ein Name mit
einem führenden Unterstrich (z. B. "_spam") sollte als nicht-
öffentlicher Teil der API behandelt werden (unabhängig davon, ob es
sich um eine Funktion, eine Methode oder ein Datenelement handelt). Er
sollte als Implementierungsdetail betrachtet werden, das sich ohne
Ankündigung ändern kann.

Da es einen berechtigten Anwendungsfall für klassenprivate Elemente
gibt (nämlich das Vermeiden von Namenskollisionen mit Namen, die von
Unterklassen definiert werden), gibt es eine begrenzte Unterstützung
für einen solchen Mechanismus, der als *Name-Mangling* bezeichnet
wird. Jeder Bezeichner der Form "__spam" (mindestens zwei führende
Unterstriche, höchstens ein nachstehender Unterstrich) wird textuell
durch "_klassenname__spam" ersetzt, wobei "klassenname" der aktuelle
Klassenname ohne führende Unterstriche ist. Dieses Umbennenung
(Mangling) erfolgt ohne Rücksicht auf die syntaktische Position des
Bezeichners, solange es innerhalb der Definition einer Klasse
auftritt.

Siehe auch:

  Die Spezifikationen zur Namensumbenennung privater Elemente für
  Details und Spezialfälle.

Die Namensumbenennung ist nützlich, um Unterklassen das Überschreiben
von Methoden zu ermöglichen, ohne interne Methodenaufrufe innerhalb
der Klasse zu beeinträchtigen. Zum Beispiel:

   class Mapping:
       def __init__(self, iterable):
           self.items_list = []
           self.__update(iterable)

       def update(self, iterable):
           for item in iterable:
               self.items_list.append(item)

       __update = update   # private Kopie der ursprünglichen Methode update()

   class MappingSubclass(Mapping):

       def update(self, keys, values):
           # provides new signature for update()
           # but does not break __init__()
           for item in zip(keys, values):
               self.items_list.append(item)

Das obige Beispiel würde selbst dann funktionieren, wenn
"MappingSubclass" einen "__update"-Bezeichner einführen würde, da
dieser in der Klasse "Mapping" durch "_Mapping__update" bzw. in der
Klasse "MappingSubclass" durch "_MappingSubclass__update" ersetzt
wird.

Beachten Sie, dass die Regeln zur Namensumbenennung hauptsächlich dazu
dienen, unbeabsichtigte Kollisionen zu vermeiden. Es ist weiterhin
möglich, auf eine Variable zuzugreifen oder diese zu verändern, die
als privat gilt. Dies kann unter besonderen Umständen sogar nützlich
sein, beispielsweise im Debugger.

Beachten Sie, dass Code, der an "exec()" oder "eval()" übergeben wird,
den Klassennamen der aufrufenden Klasse nicht als die aktuelle Klasse
ansieht. Dies ähnelt der Wirkung der "global"-Anweisung, deren Wirkung
ebenfalls auf Code beschränkt ist, der zusammen byte-kompiliert wird.
Die gleiche Einschränkung gilt für "getattr()", "setattr()" und
"delattr()" sowie für die direkte Referenzierung von "__dict__".


9.7. Dies und Das
=================

Manchmal ist ein Datentyp nützlich, der dem „record“ in Pascal oder
der „struct“ in C ähnelt und einige benannte Datenobjekte bündelt. Der
idiomatische Ansatz hierfür ist die Verwendung von "dataclasses":

   from dataclasses import dataclass

   @dataclass
   class Employee:
       name: str
       dept: str
       salary: int

   >>> john = Employee('john', 'computer lab', 1000)
   >>> john.dept
   'computer lab'
   >>> john.salary
   1000

Einem Stück Python-Code, das einen bestimmten abstrakten Datentyp
erwartet, kann oft stattdessen eine Klasse übergeben werden, die die
Methoden dieses Datentyps emuliert. Wenn du beispielsweise eine
Funktion hast, die Daten aus einem Dateiobjekt formatiert, kannst du
eine Klasse mit den Methoden "read()" und "readline()" definieren, die
die Daten stattdessen aus einem String-Puffer bezieht, und eine
Instanz davon als Argument übergeben.

Instanzmethoden-Objekte besitzen ebenfalls Attribute: "m.__self__" ist
das Instanzobjekt mit der Methode "m()", und "m.__func__" ist das der
Methode entsprechende Funktionsobjekt.


9.8. Iteratoren
===============

Inzwischen hast du wahrscheinlich bemerkt, dass über die meisten
Container-Objekte mit einer "for"-Anweisung iteriert werden kann:

   for element in [1, 2, 3]:
       print(element)
   for element in (1, 2, 3):
       print(element)
   for key in {'one':1, 'two':2}:
       print(key)
   for char in "123":
       print(char)
   for line in open("myfile.txt"):
       print(line, end='')

Diese Art des Zugriffs ist klar, prägnant und bequem. Die Verwendung
von Iteratoren durchzieht und vereinheitlicht Python. Hinter den
Kulissen ruft die "for"-Anweisung "iter()" für das Container-Objekt
auf. Die Funktion gibt ein Iterator-Objekt zurück, das die Methode
"__next__()" definiert, welche nacheinander auf die Elemente im
Container zugreift. Wenn keine Elemente mehr vorhanden sind, löst
"__next__()" eine "StopIteration"-Ausnahme aus, die der "for"-Schleife
mitteilt, dass sie beendet werden soll. Du kannst die Methode
"__next__()" über die eingebaute Funktion "next()" aufrufen. Dieses
Beispiel zeigt, wie das Ganze funktioniert::"

   >>> s = 'abc'
   >>> it = iter(s)
   >>> it
   <str_iterator object at 0x10c90e650>
   >>> next(it)
   'a'
   >>> next(it)
   'b'
   >>> next(it)
   'c'
   >>> next(it)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
       next(it)
   StopIteration

Nachdem du die Mechanik hinter dem Iterator-Protokoll gesehen hast,
ist es einfach, deinen Klassen Iterator-Verhalten hinzuzufügen.
Definiere eine "__iter__()"-Methode, die ein Objekt mit einer
"__next__()"-Methode zurückgibt. Wenn die Klasse "__next__()"
definiert, kann "__iter__()" einfach "self" zurückgeben:

   class Reverse:
       """Iterator zum Durchlaufen einer Sequenz in umgekehrter Reihenfolge."""
       def __init__(self, data):
           self.data = data
           self.index = len(data)

       def __iter__(self):
           return self

       def __next__(self):
           if self.index == 0:
               raise StopIteration
           self.index = self.index - 1
           return self.data[self.index]

   >>> rev = Reverse('spam')
   >>> iter(rev)
   <__main__.Reverse object at 0x00A1DB50>
   >>> for char in rev:
   ...     print(char)
   ...
   m
   a
   p
   s


9.9. Generatoren
================

*Generatoren* sind ein einfaches und mächtiges Werkzeug zum Erstellen
von Iteratoren. Sie werden wie gewöhnliche Funktionen geschrieben,
verwenden aber die "yield"-Anweisung, wann immer sie Daten zurückgeben
wollen. Jedes Mal, wenn "next()" darauf aufgerufen wird, setzt der
Generator an der Stelle fort, an der er aufgehört hat (er merkt sich
alle Datenwerte und welche Anweisung zuletzt ausgeführt wurde). Ein
Beispiel zeigt, dass Generatoren denkbar einfach zu erstellen sind:

   def reverse(data):
       for index in range(len(data)-1, -1, -1):
           yield data[index]

   >>> for char in reverse('golf'):
   ...     print(char)
   ...
   f
   l
   o
   g

Alles, was mit Generatoren gemacht werden kann, lässt sich auch mit
klassenbasierten Iteratoren umsetzen, wie im vorherigen Abschnitt
beschrieben. Was Generatoren so kompakt macht, ist, dass die Methoden
"__iter__()" und "__next__()" automatisch erstellt werden.

Ein weiteres Schlüsselelement ist, dass die lokalen Variablen und der
Ausführungszustand zwischen den Aufrufen automatisch gespeichert
werden. Dadurch war die Funktion einfacher zu schreiben und viel
übersichtlicher als ein Ansatz mit Instanzvariablen wie "self.index"
und "self.data".

Neben der automatischen Methodenerstellung und dem Speichern des
Programmzustands lösen Generatoren beim Beenden automatisch
"StopIteration" aus. In Kombination machen es diese Funktionen
einfach, Iteratoren mit nicht mehr Aufwand als beim Schreiben einer
normalen Funktion zu erstellen.


9.10. Generator-Ausdrücke
=========================

Einige einfache Generatoren können prägnant als Ausdrücke geschrieben
werden, wobei eine Syntax ähnlich der von List-Comprehensions
verwendet wird, jedoch mit runden statt eckigen Klammern. Diese
Ausdrücke sind für Situationen konzipiert, in denen der Generator
direkt von einer umgebenden Funktion verwendet wird. Generator-
Ausdrücke sind kompakter, aber weniger vielseitig als vollständige
Generator-Definitionen und tendenziell speicherschonender als
entsprechende List-Comprehensions.

Beispiele:

   >>> sum(i*i for i in range(10))                 # Summe der Quadrate
   285

   >>> xvec = [10, 20, 30]
   >>> yvec = [7, 5, 3]
   >>> sum(x*y for x,y in zip(xvec, yvec))         # Skalarprodukt
   260

   >>> unique_words = set(word for line in page  for word in line.split())

   >>> valedictorian = max((student.gpa, student.name) for student in graduates)

   >>> data = 'golf'
   >>> list(data[i] for i in range(len(data)-1, -1, -1))
   ['f', 'l', 'o', 'g']

-[ Fußnoten ]-

[1] Bis auf eine Sache. Modulobjekte haben ein geheimes
    schreibgeschütztes Attribut namens "__dict__", welches das
    Dictionary zurückgibt, das zur Implementierung des Namensraums des
    Moduls verwendet wird; der Name "__dict__" ist ein Attribut, aber
    kein globaler Name. Offensichtlich verletzt die Verwendung dieses
    Attributs die Abstraktion der Namensraum-Implementierung und
    sollte auf Dinge wie Post-Mortem-Debugger beschränkt werden.
