9. Klassen — Das Python3.3-Tutorial auf Deutsch (2024)

Verglichen mit anderen Programmiersprachen, fügt Pythons KlassenmechanismusKlassen mit einem Minimum an neuer Syntax und Semantik zur Sprache hinzu. Er isteine Mischung der Klassenmechanismen von C++ und Modula-3. Python Klassen bietenalle Standardeigenschaften von objektorientierter Programmierung: DerVererbungsmechanismus von Klassen erlaubt mehrere Basisklassen, eine abgeleiteteKlasse kann jegliche Methoden seiner Basisklasse(n) überschreiben und eineMethode kann die Methode der Basisklasse mit demselben Namen aufrufen. Objektekönnen beliebig viele Mengen und Arten von Daten haben. Wie es auch bei Modulender Fall ist, haben auch Klassen die dynamische Natur von Python inne: Siewerden zur Laufzeit erstellt und können auch nach der Erstellung verändertwerden.

In der Terminologie von C++ sind class members (inklusive der data member)normalerweise in Python public (Ausnahmen siehe Private Variablen) und allemember functions (Methoden) sind virtual. Wie in Modula-3 gibt es keineAbkürzung zum referenzieren von Attributen eines Objekts aus dessen Methodenheraus: Die Methode wird mit einem expliziten ersten Argument, welches dasObjekt repräsentiert, deklariert, und dann implizit beim Aufruf übergeben wird.Wie in Smalltalk sind Klassen selbst Objekte. Das bietet Semantiken zumimportieren und umbenennen. Anders als in C++ und Modula-3 können eingebauteDatentypen vom Benutzer als Basisklassen benutzt, das heisst abgeleitet, werden.Außerdem können die meisten eingebauten Operatoren, wie in C++, mit einerbesonderen Syntax (arithmetische Operatoren, Indizierung, usw.) für Instanzender Klasse neu definiert werden.

(Da es keine allgemein anerkannte Terminologie im Bezug auf Klassen gibt, werdeich zwischendurch auf Smalltalk und C++ Begriffe ausweichen. Ich würde lieberModula-3 Begriffe benutzen, da seine objektorientierte Semantik näher an Python,als an C++ ist, allerdings erwarte ich, dass wenige Leser davon gehört haben.)

9.1. Ein Wort zu Namen und Objekten

Objekte haben Individualität und mehrere Namen (in mehrerenGültigkeitsbereichen) können an dasselbe Objekt gebunden werden. In anderenSprachen wird dies als Aliasing bezeichnet. Das wird meist beim ersten Blickauf Python nicht geschätzt und kann problemlos ignoriert werden, wenn man mitunveränderbaren Datentypen (Zahlen, Zeichenketten, Tupel) arbeitet. AberAliasing hat einen möglicherweise überraschenden Effekt auf die Semantik vonPythoncode, der veränderbare Objekte wie Listen, Dictionaries oder die meistenanderen Typen, enthält. Dies kommt normalerweise dem Programm zugute, da sichAliase in mancher Hinsicht wie Pointer verhalten. Zum Beispiel ist die Übergabeeines Objekts günstig, da von der Implementierung nur ein Pointer übergebenwird. Verändert eine Funktion ein Objekt, das als Argument übergeben wurde, wirdder Aufrufende die Veränderung sehen — dies vermeidet den Bedarf an zweiverschiedenen Übergabemechanismen, wie in Pascal.

9.2. Gültigkeitsbereiche und Namensräume in Python

Bevor man Klassen überhaupt einführt, muss man über Pythons Regeln im Bezug aufGültigkeitsbereiche reden. Klassendefinitionen wenden ein paar nette Kniffe beiNamensräumen an und man muss wissen wie Gültigkeitsbereiche und Namensräumefunktionieren, um vollkommen zu verstehen was abläuft. Außerdem ist das Wissenhierüber nützlich für jeden fortgeschrittenen Pythonprogrammierer.

Fangen wir mit ein paar Definitionen an.

Ein Namensraum ist eine Zuordnung von Namen zu Objekten. Die meistenNamensräume sind momentan als Dictionaries implementiert, aber das istnormalerweise in keinerlei Hinsicht spürbar (außer bei der Performance) und kannsich in Zukunft ändern. Beispiele für Namensräume sind: Die Menge dereingebauten Namen (die Funktionen wie abs() und eingebaute Ausnahmenenthält), die globalen Namen eines Moduls und die lokalen Namen einesFunktionsaufrufs. In gewisser Hinsicht bilden auch die Attribute eines Objekteseinen Namensraum. Das Wichtigste, das man über Namensräume wissen muss, ist,dass es absolut keinen Bezug von Namen in verschiedenen Namensräumen zueinandergibt. Zum Beispiel können zwei verschiedene Module eine Funktion namensmaximize definieren, ohne dass es zu einer Verwechslung kommt, denn Benutzerdes Moduls müssen dessen Namen voranstellen.

Nebenbei bemerkt: Ich benutze das Wort Attribut für jeden Namen nach einemPunkt — zum Beispiel in dem Ausdruck z.real ist real ein Attribut desObjekts z. Genau genommen sind auch Referenzen zu Namen in ModulenAttributreferenzen: Im Ausdruck modname.funcname, ist modname einModulobjekt und funcname ein Attribut dessen. In diesem Fall gibt es einegeradlinige Zuordnung von Modulattributen und globalen Namen, die im Moduldefiniert sind: Sie teilen sich denselben Namensraum! [1]

Attribute können schreibgeschützt oder veränderbar sein. In letzterem Fall isteine Zuweisung an dieses Attribut möglich. Modulattribute sind veränderbar: Mankann modname.the_answer = 42 schreiben. Veränderbare Attribute sindgleichzeitig durch die del-Anweisung löschbar. Zum Beispiel löschtdel modname.the_answer das Attribut the_answer des Objekts namensmodname.

Namensräume werden zu verschiedenen Zeitpunkten erzeugt und haben verschiedeneLebenszeiten. Der Namensraum, der die eingebauten Namen enthält, wird beim Startdes Interpreters erzeugt und nie gelöscht. Der globale Namensraum für ein Modulwird erzeugt, wenn die Moduldefinition eingelesen wird; normalerweise existierendie Namensräume des Moduls auch solange bis der Interpreter beendet wird. DieAnweisungen, die auf oberster Ebene vom Interpreter aufgerufen werden, entwedervon einem Skript oder interaktiv gelesen, werden als Teil des Moduls__main__ behandelt, sodass sie ihren eigenen globalen Namensraum haben.(Die eingebauten Namen existieren ebenfalls in einem Modul namensbuiltins.)

Der lokale Namensraum einer Funktion wird bei deren Aufruf erstellt und wirdgelöscht, wenn sich die Funktion beendet oder eine Ausnahme auslöst, die nichtinnerhalb der Funktion behandelt wird. (Eigentlich wäre “vergessen” eine bessereBeschreibung dessen, was passiert.) Natürlich haben auch rekursive Aufrufe ihrenjeweiligen lokalen Namensraum.

Ein Gültigkeitsbereich (scope) ist eine Region eines Python-Programms, inder ein Namensraum direkt verfügbar ist, das heisst es einem unqualifiziertemNamen möglich ist einen Namen in diesem Namensraum zu finden.

Auch wenn Gültigkeitsbereiche statisch ermittelt werden, werden sie dynamischbenutzt. An einem beliebigen Zeitpunkt während der Ausführung, gibt esmindestens drei verschachtelte Gültigkeitsbereiche, deren Namensräume direktverfügbar sind:

  • Der innerste Gültigkeitsbereich, der zuerst durchsucht wird und die lokalenNamen enthält;
  • der Gültigkeitsbereich mit allen umgebenden Namensräumen (enthält auch dieglobalen Namen des momentanen Moduls), der vom nächsten umgebenden Namensraumaus durchsucht wird, und nicht-lokale, aber auch nicht-globale Namen enthält;
  • der vorletzte Gültigkeitsbereich enthält die globalen Namen des aktuellenModuls;
  • der letzte Gültigkeitsbereich (zuletzt durchsuchte) ist der Namensraum, derdie eingebauten Namen enthält.

Wird ein Name als global deklariert, so gehen alle Referenzen undZuweisungen direkt an den mittleren Gültigkeitsbereich, der die globalen Namendes Moduls enthält. Um Variablen, die außerhalb des innerstenGültigkeitsbereichs zu finden sind, neu zu binden, kann dienonlocal-Anweisung benutzt werden. Falls diese nicht alsnonlocal deklariert sind, sind diese Variablen schreibgeschützt (ein Versuchin diese Variablen zu schreiben, würde einfach eine neue lokale Variable iminnersten Gültigkeitsbereich anlegen und die äußere Variable mit demselben Namenunverändert lassen).

Normalerweise referenziert der lokale Gültigkeitsbereich die lokalen Namen dermomentanen Funktion. Außerhalb von Funktionen bezieht sich der lokaleGültigkeitsbereich auf denselben Namensraum wie der globale Gültigkeitsbereich:Den Namensraum des Moduls. Klassendefinitionen stellen einen weiterenNamensraum im lokalen Gültigkeitsbereich dar.

Es ist wichtig zu verstehen, dass die Gültigkeitsbereiche am Text ermitteltwerden: Der globale Gültigkeitsbereich einer Funktion, die in einem Moduldefiniert wird, ist der Namensraum des Moduls, ganz egal wo die Funktionaufgerufen wird. Andererseits wird die tatsächliche Suche nach Namen dynamischzur Laufzeit durchgeführt — jedoch entwickelt sich die Definition der Sprachehin zu einer statischen Namensauflösung zur Kompilierzeit, deshalb sollte mansich nicht auf die dynamische Namensauflösung verlassen! (In der Tat werdenlokale Variablen schon statisch ermittelt.)

Eine besondere Eigenart Pythons ist, dass – wenn keineglobal-Anweisung aktiv ist – Zuweisungen an Namen immer im innerstenGültigkeitsbereich abgewickelt werden. Zuweisungen kopieren keineDaten, sondern binden nur Namen an Objekte. Das gleiche gilt für Löschungen: DieAnweisung del x entfernt nur die Bindung von x aus dem Namensraum deslokalen Gültigkeitsbereichs. In der Tat benutzen alle Operationen, die neueNamen einführen, den lokalen Gültigkeitsbereich: Im Besonderen bindenimport-Anweisungen und Funktionsdefinitionen das Modulbeziehungsweise den Funktionsnamen im lokalen Gültigkeitsbereich.

Die global-Anweisung kann benutzt werden, um anzuzeigen, dassbestimmte Variablen im globalen Gültigkeitsbereich existieren und hierneu gebunden werden sollen. Die nonlocal-Anweisung zeigt an, dasseine bestimmte Variable im umgebenden Gültigkeitsbereich existiert und hierneu gebunden werden soll.

9.2.1. Beispiel zu Gültigkeitsbereichen und Namensräumen

Dies ist ein Beispiel, das zeigt, wie man die verschiedenen Gültigkeitsbereicheund Namensräume referenziert und wie global und :keyword`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("Nach der lokalen Zuweisung:", spam) do_nonlocal() print("Nach der nonlocal Zuweisung:", spam) do_global() print("Nach der global Zuweisung:", spam)scope_test()print("Im globalen Gültigkeitsbereich:", spam)

Die Ausgabe des Beispielcodes ist:

.. code-block:: none Nach der lokalen Zuweisung: test spam Nach der nonlocal Zuweisung: nonlocal spam Nach der global Zuweisung: nonlocal spam Im globalen Gültigkeitsbereich: global spam

Beachte, dass die lokale Zuweisung (was der Standard ist) die Bindung vonspam in scope_test nicht verändert hat. Die nonlocal Zuweisungdie Bindung von spam in scope_test und die global Zuweisung dieBindung auf Modulebene verändert hat.

Man kann außerdem sehen, dass es keine vorherige Bindung von spam vor derglobal Zuweisung gab.

9.3. Eine erste Betrachtung von Klassen

Klassen führen ein kleines bisschen neue Syntax, drei neue Objekttypen und einwenig neue Semantik ein.

9.3.1. Syntax der Klassendefinition

Die einfachste Form einer Klassendefinition sieht so aus:

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

Klassendefinitionen müssen wie Funktionsdefinitionen(def-Anweisungen) ausgeführt werden, bevor sie irgendwelcheAuswirkungen haben. (Es wäre vorstellbar eine Klassendefinition in einen Zweigeiner if-Anweisung oder in eine Funktion zu platzieren.)

In der Praxis sind die Anweisungen innerhalb einer Klassendefinitionüblicherweise Funktionsdefinitionen, aber andere Anweisungen sind erlaubt undmanchmal nützlich — dazu kommen wir später noch. Die Funktionsdefinitioneninnerhalb einer Klasse haben normalerweise eine besondere Argumentliste, dievon den Aufrufkonventionen für Methoden vorgeschrieben wird — das wirdwiederum später erklärt.

Wird eine Klassendefinition betreten, wird ein neuer Namensraum erzeugt und alslokaler Gültigkeitsbereich benutzt — deshalb werden Zuweisungen an lokaleVariablen in diesem neuen Namensraum wirksam. Funktionsdefinitionen binden denNamen der neuen Funktion ebenfalls dort.

Wird eine Klassendefinition normal verlassen (indem sie endet), wird einKlassenobjekt erstellt. Dies ist im Grunde eine Verpackung um den Inhalt desNamensraums, der von der Klassendefinition erstellt wurde. Im nächsten Abschnittlernen wir mehr darüber. Der ursprüngliche lokale Gültigkeitsbereich (der vordem Betreten der Klassendefinition aktiv war) wird wiederhergestellt und dasKlassenobjekt wird in ihm an den Namen, der im Kopf der Klassendefinitionangegeben wurde, gebunden (ClassName in unserem Beispiel).

9.3.2. Klassenobjekte

Klassenobjekte unterstützen zwei Arten von Operationen: Attributreferenzierungenund Instanziierung.

Attributreferenzierungen benutzen die normale Syntax, die für alleAttributreferenzen in Python benutzt werden: obj.name. Gültige Attributesind alle Namen, die bei der Erzeugung des Klassenobjektes im Namensraum derKlasse waren. Wenn die Klassendefinition also so aussah:

class MyClass: """A simple example class""" i = 12345 def f(self): return 'Hallo Welt'

dann sind MyClass.i und MyClass.f gültige Attributreferenzen, die eineGanzzahl beziehungsweise ein Funktionsobjekt zurückgeben. Zuweisungen anKlassenattribute sind ebenfalls möglich, sodass man den Wert von MyClass.idurch Zuweisung verändern kann. __doc__ ist ebenfalls ein gültigesAttribut, das den Docstring, der zur Klasse gehört, enthält: "A simple exampleclass".

Klassen Instanziierung benutzt die Funktionsnotation. Tu einfach so, als obdas Klassenobjekt eine parameterlose Funktion wäre, die eine neue Instanz derKlasse zurückgibt. Zum Beispiel (im Fall der obigen Klasse):

x = MyClass()

Dies erzeugt eine neue Instanz der Klasse und weist dieses Objekt der lokalenVariable x zu.

Die Instanziierungsoperation (“aufrufen” eines Klassenobjekts) erzeugt ein leeresObjekt. Viele Klassen haben es gerne Instanzobjekte, die auf einen spezifischenAnfangszustand angepasst wurden, zu erstellen. Deshalb kann eine Klasse einespezielle Methode namens __init__(), wie folgt definieren:

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

Definiert eine Klasse eine __init__()-Methode, ruft dieKlasseninstanziierung automatisch __init__() für die neu erstellteKlasseninstanz auf. So kann in diesem Beispiel eine neue, initialisierte Instanzdurch folgendes bekommen werden:

x = MyClass()

Natürlich kann die __init__()-Methode Argumente haben, um eine größereFlexibilität zu erreichen. In diesem Fall werden die, demKlasseninstanziierungsoperator übergebenen Argumente an __init__()weitergereicht. 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 jetzt mit den Instanzobjekten tun? Die einzigen Operationen, dieInstanzobjekte verstehen, sind Attributreferenzierungen. Es gibt zwei Artengültiger Attribute: Datenattribute und Methoden.

Datenattribute entsprechen “Instanzvariablen” in Smalltalk und “data members”in C++. Datenattribute müssen nicht deklariert werden; wie lokale Variablenerwachen sie zum Leben, sobald ihnen zum ersten Mal etwas zugewiesen wird. ZumBeispiel wird folgender Code, unter der Annahme, dass x die Instanz vonMyClass ist, die oben erstellt wurde, den Wert 16 ausgeben, ohneSpuren zu hinterlassen:

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

Die andere Art von Instanzattribut ist die Methode. Eine Methode ist eineFunktion, die zu einem Objekt gehört. (In Python existiert der Begriff Methodenicht allein für Klasseninstanzen: Andere Objekttypen können genauso Methodenhaben. Zum Beispiel haben Listenobjekte Methoden namens append(),insert(), remove(), sort(), und so weiter. Jedoch benutzen wirin der folgenden Diskussion den Begriff Methode ausschliesslich im Sinne vonMethoden von Klasseninstanzobjekten, sofern nichts anderes angegeben ist.

Ob ein Attribut eine gültige Methode ist, hängt von der Klasse ab. PerDefinition definieren alle Attribute, die ein Funktionsobjekt sind, einentsprechendes Methodenobjekt für seine Instanz. Deshalb ist in unserem Beispielx.f eine gültige Methodenreferenz, da MyClass.f eine Funktion ist, aberx.i ist keine, da MyClass.i es nicht ist. x.f ist aber nichtdasselbe wie MyClass.f — es ist ein Methodenobjekt und keinFunktionsobjekt.

9.3.4. Methodenobjekte

Üblicherweise wird eine Methode gemäß seiner Bindung aufgerufen:

x.f()

Im MyClass Beispiel wird dies die Zeichenkette 'Hallo Welt'ausgeben. Jedoch ist es nicht notwendig eine Methode direkt aufzurufen: x.fist ein Methodenobjekt und kann weg gespeichert werden und später wiederaufgerufen werden. Zum Beispiel:

xf = x.fwhile True: print(xf())

Das wird bis zum Ende der Zeit Hallo Welt ausgeben.

Was passiert genau, wenn eine Methode aufgerufen wird? Du hast vielleichtbemerkt, dass x.f() oben ohne Argument aufgerufen wurde, obwohl in derFunktionsdefinition für f() ein Argument festgelegt wurde. Was ist mitdiesem Argument passiert? Natürlich verursacht Python eine Ausnahme, wenn eineFunktion, die ein Argument benötigt ohne aufgerufen wird — auch wenn dasArgument eigentlich gar nicht genutzt wird ...

Tatsächlich, wie du vielleicht schon erraten hast, ist die Besonderheit beiMethoden, dass das Objekt als erstes Argument der Funktion übergeben wird. Inunserem Beispiel ist der Aufruf x.f() das genaue äquivalent vonMyClass.f(x). Im Allgemeinen ist der Aufruf einer Methode mit n Argumentenäquivalent zum Aufruf der entsprechenden Funktion mit einer Argumentliste, diedurch das Einfügen des Objekts der Methode vor das erste Argument erzeugt wird.

Verstehst du immernoch nicht, wie Methoden funktionieren, hilft vielleicht einBlick auf die Implementierung, um die Dinge zu klären. Wenn ein Instanzattributreferenziert wird, das kein Datenattribut ist, wird seine Klasse durchsucht.Bezeichnet der Name ein gültiges Klassenattribut, das eine Funktion ist, wirdein Methodenobjekt erzeugt, indem (Zeiger zu) Instanzobjekt und Funktionsobjektzu einem abstrakten Objekt verschmolzen werden: Dies ist das Methodenobjekt.Wird das Methodenobjekt mit einer Argumentliste aufgerufen, wird es wiederentpackt, eine neue Argumentliste aus dem Instanzobjekt und der ursprünglichenArgumentliste erzeugt und das Funktionsobjekt mit dieser neuen Argumentlisteaufgerufen.

9.4. Beiläufige Anmerkungen

Datenattribute überschreiben Methodenattribute desselben Namens. Um zufälligeNamenskonflikte zu vermeiden, die zu schwer auffindbaren Fehlern in großenProgrammen führen, ist es sinnvoll sich auf irgendeine Konvention zuverständigen, die das Risiko solcher Konflikte vermindern. Mögliche Konventionenbeinhalten das Großschreiben von Methodennamen, das Voranstellen von kleineneindeutigen Zeichenketten (vielleicht auch nur ein Unterstrich) beiDatenattributen oder das Benutzen von Verben bei Methodennamen und Nomen beiDatenattributen.

Datenattribute können von Methoden, genauso wie von normalen Benutzern(“clients”) eines Objektes referenziert werden. In anderen Worten: Klassen sindnicht benutzbar, um reine abstrakte Datentypen (“abstract data types”) zuimplementieren. In Wirklichkeit, gibt es in Python keine Möglichkeit umDatenkapselung (data hiding) zu erzwingen — alles basiert auf Konventionen.(Auf der anderen Seite kann die Python-Implementierung, in C geschrieben,Implementationsdetails komplett verstecken und den Zugriff auf ein Objektkontrollieren, wenn das nötig ist; das kann von in C geschriebenenPython-Erweiterungen ebenfalls benutzt werden.)

Clients sollten Datenattribute mit Bedacht nutzen, denn sie könnten Invariantenkaputt machen, die von Methoden verwaltet werden, indem sie auf derenDatenattributen herumtrampeln. Man sollte beachten, dass Clients zu ihremeigenen Instanzobjekt Datenattribute hinzufügen können, ohne die Gültigkeit derMethoden zu gefährden, sofern Namenskonflikte vermieden werden — auch hierkann eine Bennenungskonvention viele Kopfschmerzen ersparen.

Es gibt keine Abkürzung, um Datenattribute (oder andere Methoden!) innerhalb vonMethoden zu referenzieren. Meiner Meinung verhilft das Methoden zu bessererLesbarkeit: Man läuft keine Gefahr, lokale und Instanzvariablen zu verwechseln,wenn man eine Methode überfliegt.

Oft wird das erste Argument einer Methode self genannt. Dies ist nichtsanderes als eine Konvention: Der Name self hat absolut keine spezielleBedeutung für Python. Aber beachte: Hälst du dich nicht an die Konvention, kanndein Code schwerer lesbar für andere Python-Programmierer sein und es ist auchvorstellbar, dass ein Klassenbrowser (class browser) sich auf dieseKonvention verlässt.

Jedes Funktionsobjekt, das ein Klassenattribut ist, definiert eine Methode fürInstanzen dieser Klasse. Es ist nicht nötig, dass die Funktionsdefinition imText innerhalb der Klassendefinition ist: Die Zuweisung eines Funktionsobjektesan eine lokale Variable innerhalb der Klasse ist ebenfalls in Ordnung. ZumBeispiel:

# Funktionsdefintion außerhalb der Klassedef f1(self, x, y): return min(x, x+y)class C: f = f1 def g(self): return 'Hallo Welt' h = g

f, g und h sind jetzt alle Attribute der Klasse C, dieFunktionsobjekte referenzieren und somit sind sie auch alle Methoden derInstanzen von Ch ist dabei gleichbedeutend mit g. Beachteaber, dass diese Praxis nur dazu dient einen Leser des Programms zu verwirren.

Methoden können auch andere Methoden aufrufen, indem sie das Methodenattributdes Arguments self benutzen:

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

Methoden können globale Namen genauso wie normale Funktionen referenzieren. Derglobale Gültigkeitsbereich der Methode ist das Modul, das die Klassendefinitionenthält. (Eine Klasse selbst wird nie als globaler Gültigkeitsbereich benutzt.)Während man selten einen guten Grund dafür hat globale Daten zu benutzen, gibtes viele berechtigte Verwendungen des globalen Gültigkeitsbereichs: Zum einenkönnen Funktionen und Module, die in den globalen Gültigkeitsbereich importiertwerden, genauso wie Funktionen und Klassen die darin definiert werden, von derMethode benutzt werden. Normalerweise ist die Klasse, die die Methode enthält,selbst in diesem globalen Gültigkeitsbereich definiert und im nächsten Abschnittwerden wir ein paar gute Gründe entdecken, warum eine Methode die eigene Klassereferenzieren wollte.

Jeder Wert ist ein Objekt und hat deshalb eine Klasse (auch type genannt).Es wird als Objekt.__class__ abgelegt.

9.5. Vererbung

Natürlich verdient ein Sprachmerkmal nicht den Namen “Klasse”, wenn es nichtVererbung unterstützt. Die Syntax für eine abgeleitete Klassendefinition siehtso aus:

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

Der Name BaseClassName muss innerhalb des Gültigkeitsbereichs, der dieabgeleitete Klassendefinition enthält, definiert sein. Anstelle einesBasisklassennamens sind auch andere willkürliche Ausdrücke erlaubt. Dies kannbeispielsweise nützlich sein, wenn die Basisklasse in einem anderen Moduldefiniert ist:

class DerivedClassName(modname.BaseClassName):

Die Ausführung einer abgeleiteten Klassendefinition läuft genauso wie bei einerBasisklasse ab. Bei der Erzeugung des Klassenobjekts, wird sich der Basisklasseerinnert. Dies wird zum Auflösen der Attributsreferenzen benutzt: Wird einangefordertes Attribut nicht innerhalb der Klasse gefunden, so wird in derBasisklasse weitergesucht. Diese Regel wird rekursiv angewandt, wenn dieBasisklasse selbst von einer anderen Klasse abgeleitet wird.

Es gibt nichts besonderes an der Instanziierung von abgeleiteten Klassen:DerivedClassName erzeugt eine neue Instanz der Klasse. Methodenreferenzenwerden wie folgt aufgelöst: Das entsprechende Klassenattribut wird durchsucht,falls nötig bis zum Ende der Basisklassenkette hinab und die Methodenreferenzist gültig, wenn es ein Funktionsobjekt bereithält.

Abgeleitete Klassen können Methoden ihrer Basisklassen überschreiben. DaMethoden keine besonderen Privilegien beim Aufrufen anderer Methoden desselbenObjekts haben, kann eine Methode einer Basisklasse, die eine andere Methode, diein derselben Basisklasse definiert wird, aufruft, beim Aufruf einer Methode derabgeleiteten Klasse landen, die sie überschreibt. (Für C++-Programmierer: AlleMethoden in Python sind im Grunde virtual.)

Eine überschreibende Methode in einer abgeleiteten Klasse wird in der Tat eherdie Methode der Basisklasse mit demselben Namen erweitern, statt einfach nur zuersetzen. Es gibt einen einfachen Weg die Basisklassenmethode direkt aufzurufen:Einfach BaseClassName.methodname(self, arguments) aufrufen. Das istgelegentlich auch für Clients nützlich. (Beachte, dass dies nur funktioniert,wenn die Basisklasse als BaseClassName im globalen Gültigkeitsbereichzugänglich ist.)

Python hat zwei eingebaute Funktionen, die mit Vererbung zusammenarbeiten:

  • Man benutzt isinstance() um den Typ eines Objekts zu überprüfen:isinstance(obj, int) ist nur dann True, wenn obj.__class__ vom Typint oder einer davon abgeleiteten Klasse ist.
  • Man benutzt issubclass() um Klassenvererbung zu überprüfen:issubclass(bool, int) ist True, da bool eine von intabgeleitete Klasse ist. Jedoch ist issubclass(float, int) False, dafloat keine von int abgeleitete Klasse ist.

9.5.1. Mehrfachvererbung

Python unterstützt auch eine Form der Mehrfachvererbung. Eine Klassendefinitionmit mehreren Basisklassen sieht so aus:

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

Für die meisten Zwecke, im einfachsten Fall, kann man sich die Suche nachgeerbten Attributen von einer Elternklasse so vorstellen: Zuerst in die Tiefe(depth-first), von links nach rechts (left-to-right), wobei nicht zweimal inderselben Klasse gesucht wird, wenn sich die Klassenhierarchie dort überlappt.Deshalb wird, wenn ein Attribut nicht in DerivedClassName gefundenwird, danach in Base1 gesucht, dann (rekursiv) in den Basisklassen vonBase1 und wenn es dort nicht gefunden wurde, wird in Base2gesucht, und so weiter.

In Wirklichkeit ist es ein wenig komplexer als das, denn die Reihenfolge derMethodenauflösung (method resolution order - MRO) wird dynamisch verändert, umzusammenwirkende Aufrufe von super() zu unterstützen. Dieser Ansatz wirdin manchen anderen Sprachen als call-next-method (Aufruf der nächsten Methode)bekannt und ist mächtiger als der super-Aufruf, den es in Sprachen miteinfacher Vererbung gibt.

Es ist nötig dynamisch zu ordnen, da alle Fälle von Mehrfachvererbung eine odermehrere Diamantbeziehungen aufweisen (bei der auf mindestens eine derElternklassen durch mehrere Pfade von der untersten Klasse aus zugegriffenwerden kann). Zum Beispiel erben alle Klassen von object und so stelltjeder Fall von Mehrfachvererbung mehrere Wege bereit, um objectzu erreichen. Um zu verhindern, dass auf die Basisklassen mehr als einmalzugegriffen werden kann, linearisiert der dynamische Algorithmus dieSuchreihenfolge, sodass die Ordnung von links nach rechts, die in jeder Klassefestgelegt wird, jede Elternklasse nur einmal aufruft und zwar monoton (in derBedeutung, dass eine Klasse geerbt werden kann, ohne das die Rangfolge seiner Elternberührt wird). Zusammengenommen machen diese Eigenschaften es möglichverlässliche und erweiterbare Klassen mit Mehrfachvererbung zu entwerfen. FürDetails, siehe http://www.python.org/download/releases/2.3/mro/.

9.6. Private Variablen

“Private” Instanzvariablen, die nur innerhalb des Objekts zugänglich sind, gibtes in Python nicht. Jedoch gibt es eine Konvention, die im meisten Python-Codebefolgt wird: Ein Name, der mit einem Unterstrich beginnt (z.B. _spam)sollte als nicht-öffentlicher Teil der API behandelt werden (egal ob es eineFunktion, eine Methode oder ein Datenattribut ist). Es sollte alsImplementierungsdetails behandelt werden, das sich unangekündigt ändern kann.

Da es eine sinnvolle Verwendung für klassen-private Attribute gibt, umNamenskonflikte mit Namen, die von Unterklassen definiert werden zu vermeiden,gibt es eine begrenzte Unterstützung für so einen Mechanismus: namemangling (Namensersetzung). Jeder Bezeichner der Form __spam (mindestenszwei führende Unterstriche, höchstens ein folgender) wird im Text durch_classname__spam ersetzt, wobei classname der Name der aktuellen Klasse(ohne eventuelle führende Unterstriche) ist. Die Ersetzung geschieht ohneRücksicht auf die syntaktische Position des Bezeichners, sofern er innerhalb derDefinition der Klasse steht.

Namensersetzung ist hilfreich, um Unterklassen zu ermöglichen Methoden zuüberschreiben, ohne dabei Methodenaufrufe innerhalb der Klasse zu stören. ZumBeispiel:

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 update() Methodeclass MappingSubclass(Mapping): def update(self, keys, values): # erstellt update() mit neuer Signatur # macht aber __init__() nicht kaputt for item in zip(keys, values): self.items_list.append(item)

Beachte, dass die Ersetzungsregeln vor allem dazu gedacht sind, Unfälle zuvermeiden; es ist immernoch möglich auf einen solchen als privatgekennzeichneten Namen von aussen zuzugreifen und ihn auch zu verändern. Daskann in manchen Umständen sogar nützlich sein, beispielsweise in einem Debugger.

Beachte, dass Code, der von exec() oder eval() ausgeführt wird, denKlassennamen der aufrufenden Klasse nicht als die aktuelle Klasse ansieht. Diesähnelt dem Effekt der global-Anweisung, der ebenfalls sehr beschränktauf den Code ist, der zusammen byte-kompiliert wird. Die gleiche Begrenzunggilt für getattr(), setattr() und delattr(), sowie den direktenZugriff auf __dict__.

9.7. Kleinkram

Manchmal ist es nützlich einen Datentyp zu haben, der sich ähnlich demrecord in Pascal oder dem “struct” in C verhält und ein Container für einpaar Daten ist. Hier bietet sich eine leere Klassendefinition an:

class Employee: passjohn = Employee() # Eine leere Arbeitnehmerakte anlegen# Die Akte ausfüllenjohn.name = 'John Doe'john.dept = 'Computerraum'john.salary = 1000

Einem Stück Python-Code, der einen bestimmten abstrakten Datentyp erwartet, kannstattdessen oft eine Klasse übergeben werden, die die Methoden dieses Datentypsemuliert. Wenn man zum Beispiel eine Funktion hat, die Daten aus einemDateiobjekt formatiert, kann man eine Klasse mit den Methoden read() undreadline() definieren, die die Daten stattdessen aus einemZeichenkettenpuffer bekommt, und als Argument übergeben.

Methodenobjekte der Instanz haben auch Attribute: m.__self__ ist dasInstanzobjekt mit der Methode m() und m.__func__ ist das entsprechendeFunktionsobjekt der Methode.

9.8. Ausnahmen sind auch Klassen

Benutzerdefinierte Ausnahmen werden auch durch Klassen gekennzeichnet. Durch dieNutzung dieses Mechanismus ist es möglich erweiterbare Hierarchien vonAusnahmen zu erstellen.

Es gibt zwei neue (semantisch) gültige Varianten derraise-Anweisung:

raise Klasseraise Instanz

In der ersten Variante muss Class eine Instanz von type oder einerdavon abgeleiteten Klasse sein und ist eine Abkürzung für:

raise Klasse()

Die in einem except-Satz angegebene Klasse fängt Ausnahmen dann ab,wenn sie Instanzen derselben Klasse sind oder von dieser abgeleitet wurden,nicht jedoch andersrum — der mit einer abgeleiteten Klasse angegebeneexcept-Satz fängt nicht die Basisklasse ab. Zum Beispiel gibt derfolgende Code B, C, D in dieser Reihenfolge aus:

class B(Exception): passclass C(B): passclass D(C): passfor cls in [B, C, D]: try: raise cls() except D: print("D") except C: print("C") except B: print("B")

Beachte, dass B, B, B ausgegeben wird, wenn man die Reihenfolge umdreht, dasheisst zuerst except B, da der erste zutreffende except-Satzausgelöst wird.

Wenn eine Fehlermeldung wegen einer unbehandelten Ausnahme ausgegeben wird, wirdder Name der Klasse, danach ein Doppelpunkt und ein Leerzeichen und schliesslichdie Instanz mit Hilfe der eingebauten Funktion str() zu einer Zeichenketteumgewandelt ausgegeben.

9.9. Iteratoren

Mittlerweile hast du wahrscheinlich bemerkt, dass man über die meistenContainerobjekte mit Hilfe von for iterieren kann:

for element in [1, 2, 3]: print(element)for element in (1, 2, 3): print(element)for key in {'eins':1, 'zwei':2}: print(key)for char in "123": print(char)for line in open("meinedatei.txt"): print(line)

Diese Art des Zugriffs ist klar, präzise und praktisch. Der Gebrauch vonIteratoren durchdringt und vereinheitlicht Python. Hinter den Kulissen ruft diefor-Anweisung iter() für das Containerobjekt auf. Die Funktiongibt ein Iteratorobjekt zurück, das die Methode __next__() definiert,die auf die Elemente des Containers nacheinander zugreift. Gibt es keineElemente mehr, verursacht __next__() eine StopIteration-Ausnahme,die der for-Schleife mitteilt, dass sie sich beenden soll. Man kannauch die __next__()-Methode mit Hilfe der eingebauten Funktionnext() aufrufen. Folgendes Beispiel demonstriert, wie alles funktioniert.

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

Kennt man die Mechanismen hinter dem Iterator-Protokoll, ist es einfach dasVerhalten von Iteratoren eigenen Klassen hinzuzufügen. Man definiert eine__iter__()-Methode, die ein Objekt mit einer __next__()-Methodezurückgibt. Definiert die Klasse __next__(), kann __iter__() einfachself zurückgeben:

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)...maps

9.10. Generatoren

Generatoren (generator) sind eine einfache aber mächtige Möglichkeit umIteratoren zu erzeugen. Generatoren werden wie normale Funktionen geschrieben,benutzen aber yield, um Daten zurückzugeben. Jedes Mal wennnext() aufgerufen wird, fährt der Generator an der Stelle fort, an der erzuletzt verlassen wurde (der Generator merkt sich dabei die Werte allerVariablen und welche Anweisung zuletzt ausgeführt wurde). Das nachfolgendeBeispiel zeigt wie einfach die Erstellung von Generatoren ist:

def reverse(data): for index in range(len(data)-1, -1, -1): yield data[index]
>>> for char in reverse('golf'):...  print(char)...flog

Alles, was mit Generatoren möglich ist, kann ebenso (wie im vorigen Abschnittdargestellt) mit Klassen-basierten Iteratoren, umgesetzt werden. Generatorenerlauben jedoch eine kompaktere Schreibweise, da die Methoden __iter__()und __next__() automatisch erstellt werden.

Des weiteren werden die lokalen Variablen und der Ausführungsstand automatischzwischen den Aufrufen gespeichert. Das macht das Schreiben der Funktion einfacherund verständlicher als ein Ansatz, der mit Instanzvariablen wie self.indexoder self.data arbeitet.

Generatoren werfen automatisch StopIteration, wenn sie terminieren.Zusammengenommen ermöglichen diese Features die Erstellung von Iteratoren miteinem Aufwand, der nicht größer als die Erstellung einer normalen Funktion ist.

9.11. Generator Ausdrücke

Manche einfachen Generatoren können prägnant als Ausdrücke mit Hilfe einerSyntax geschrieben werden, die der von List Comprehensions ähnlich ist, jedochmit runden, statt eckigen Klammern. Diese Ausdrücke sind für Situationengedacht, in denen der Generator gleich von der umgebenden Funktion genutzt wird.Generator Ausdrücke sind kompakter, aber auch nicht so flexibel wie ganzeGeneratordefinitionen und neigen dazu speicherschonender als die entsprechendenList Comprehensions zu sein.

Beispiele:

>>> sum(i*i for i in range(10)) # Summe der Quadrate285>>> xvec = [10, 20, 30]>>> yvec = [7, 5, 3]>>> sum(x*y for x,y in zip(xvec, yvec)) # Skalarprodukt260>>> from math import pi, sin>>> sine_table = {x: sin(x*pi/180) for x in range(0, 91)}>>> 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 Ausnahme: Modulobjekte haben ein geheimes, schreibgeschützesAttribut namens __dict__, das das Dictionary darstellt, mit dem derNamensraum des Modules implementiert wird; der Name __dict__` ist einAttribut, aber kein globaler Name. Offensichtlich ist dessen Benutzung eineVerletzung der Abstraktion der Namensraumimplementation und sollte deshalbauf Verwendungen wie die eines Post-Mortem-Debuggers reduziert werden.
9. Klassen — Das Python3.3-Tutorial auf Deutsch (2024)
Top Articles
Latest Posts
Article information

Author: Pres. Lawanda Wiegand

Last Updated:

Views: 5969

Rating: 4 / 5 (51 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Pres. Lawanda Wiegand

Birthday: 1993-01-10

Address: Suite 391 6963 Ullrich Shore, Bellefort, WI 01350-7893

Phone: +6806610432415

Job: Dynamic Manufacturing Assistant

Hobby: amateur radio, Taekwondo, Wood carving, Parkour, Skateboarding, Running, Rafting

Introduction: My name is Pres. Lawanda Wiegand, I am a inquisitive, helpful, glamorous, cheerful, open, clever, innocent person who loves writing and wants to share my knowledge and understanding with you.