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 integrierte Typen als Basisklassen für Erweiterungen durch den Benutzer verwendet werden. Zudem können, wie in C++, die meisten integrierten 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 integrierten Namen (die Funktionen wie abs() und integrierte 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
Attributes may be read-only or writable. In the latter case, assignment to
attributes is possible. Module attributes are writable: you can write
modname.the_answer = 42. Writable attributes may also be deleted with the
del statement. For example, del modname.the_answer will remove
the attribute the_answer from the object named by modname.
Namensräume werden zu unterschiedlichen Zeitpunkten erstellt und haben unterschiedliche Lebensdauern. Der Namensraum, der die integrierten 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 integrierten 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 integrierten 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.
When a class definition is left normally (via the end), a class object is
created. This is basically a wrapper around the contents of the namespace
created by the class definition; we’ll learn more about class objects in the
next section. The original local scope (the one in effect just before the class
definition was entered) is reinstated, and the class object is bound here to the
class name given in the class definition header (ClassName in the
example).
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'
then MyClass.i and MyClass.f are valid attribute references, returning
an integer and a function object, respectively. Class attributes can also be
assigned to, so you can change the value of MyClass.i by assignment.
__doc__ is also a valid attribute, returning the docstring belonging to
the class: "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.
The instantiation operation („calling“ a class object) creates an empty object.
Many classes like to create objects with instances customized to a specific
initial state. Therefore a class may define a special method named
__init__(), like this:
def __init__(self):
self.data = []
When a class defines an __init__() method, class instantiation
automatically invokes __init__() for the newly created class instance. So
in this example, a new, initialized instance can be obtained by:
x = MyClass()
Of course, the __init__() method may have arguments for greater
flexibility. In that case, arguments given to the class instantiation operator
are passed on to __init__(). For example,
>>> 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
The other kind of instance attribute reference is a method. A method is a function that „belongs to“ an object. (In Python, the term method is not unique to class instances: other object types can have methods as well. For example, list objects have methods called append, insert, remove, sort, and so on. However, in the following discussion, we’ll use the term method exclusively to mean methods of class instance objects, unless explicitly stated otherwise.)
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()
In the MyClass example, this will return the string 'hello world'.
However, it is not necessary to call a method right away: x.f is a method
object, and can be stored away and called at a later time. For example:
xf = x.f
while True:
print(xf())
gibt bis ans Ende der Zeit weiterhin hello world aus.
What exactly happens when a method is called? You may have noticed that
x.f() was called without an argument above, even though the function
definition for f() specified an argument. What happened to the argument?
Surely Python raises an exception when a function that requires an argument is
called without any — even if the argument isn’t actually used…
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.
If you still don’t understand how methods work, a look at the implementation can perhaps clarify matters. When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.
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'
As discussed in Ein Wort zu Namen und Objekten, shared data can have possibly surprising effects with involving mutable objects such as lists and dictionaries. For example, the tricks list in the following code should not be used as a class variable because just a single list would be shared by all Dog instances:
class Dog:
tricks = [] # mistaken use of a class variable
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 # unexpectedly shared by all dogs
['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 = [] # creates a new empty list for each dog
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:
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
Now f, g and h are all attributes of class C that refer to
function objects, and consequently they are all methods of instances of
C — h being exactly equivalent to g. Note that this practice
usually only serves to confuse the reader of a program.
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>
The name BaseClassName must be defined in a scope containing the
derived class definition. In place of a base class name, other arbitrary
expressions are also allowed. This can be useful, for example, when the base
class is defined in another module:
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, arguments) 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 integrierte Funktionen, die mit Vererbung arbeiten:
Verwende
isinstance(), um den Typ einer Instanz zu überprüfen:isinstance(obj, int)ist nur dannTrue, wennobj.__class__gleichintoder einer vonintabgeleiteten Klasse ist.Verwende
issubclass(), um die Klassenvererbung zu überprüfen:issubclass(bool, int)istTrue, dabooleine Unterklasse vonintist.issubclass(float, int)ist jedochFalse, dafloatkeine Unterklasse vonintist.
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>
For most purposes, in the simplest cases, you can think of the search for
attributes inherited from a parent class as depth-first, left-to-right, not
searching twice in the same class where there is an overlap in the hierarchy.
Thus, if an attribute is not found in DerivedClassName, it is searched
for in Base1, then (recursively) in the base classes of Base1,
and if it was not found there, it was searched for in Base2, and so on.
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.
Dynamic ordering is necessary because all cases of multiple inheritance exhibit
one or more diamond relationships (where at least one of the parent classes
can be accessed through multiple paths from the bottommost class). For example,
all classes inherit from object, so any case of multiple inheritance
provides more than one path to reach object. To keep the base classes
from being accessed more than once, the dynamic algorithm linearizes the search
order in a way that preserves the left-to-right ordering specified in each
class, that calls each parent only once, and that is monotonic (meaning that a
class can be subclassed without affecting the precedence order of its parents).
Taken together, these properties make it possible to design reliable and
extensible classes with multiple inheritance. For more detail, see
https://www.python.org/download/releases/2.3/mro/.
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 _classname__spam ersetzt, wobei classname der aktuelle Klassenname ohne führende Unterstriche ist. Diese Umbenennung (Mangling) erfolgt ohne Rücksicht auf die syntaktische Position des Bezeichners, solange es innerhalb der Definition einer Klasse auftritt.
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 copy of original update() method
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.
Beachte, 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.
Beachte, 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
A piece of Python code that expects a particular abstract data type can often be
passed a class that emulates the methods of that data type instead. For
instance, if you have a function that formats some data from a file object, you
can define a class with methods read() and readline() that get the
data from a string buffer instead, and pass it as an argument.
Instance method objects have attributes, too: m.__self__ is the instance
object with the method m(), and m.__func__ is the function object
corresponding to the method.
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 integrierte 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
Having seen the mechanics behind the iterator protocol, it is easy to add
iterator behavior to your classes. Define an __iter__() method which
returns an object with a __next__() method. If the class
defines __next__(), then __iter__() can just return self:
class Reverse:
"""Iterator for looping over a sequence backwards."""
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
Anything that can be done with generators can also be done with class-based
iterators as described in the previous section. What makes generators so
compact is that the __iter__() and __next__() methods
are created automatically.
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)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
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
Except for one thing. Module objects have a secret read-only attribute called
__dict__which returns the dictionary used to implement the module’s namespace; the name__dict__is an attribute but not a global name. Obviously, using this violates the abstraction of namespace implementation, and should be restricted to things like post-mortem debuggers.