Klassen
objektorientierte Programmierung
Auch wenn Python ohne wenn und aber eine ojektorientierte Programmiersprache ist,
haben wir es bisher mit voller Absicht in den vorhergehenden Kapitel unseres
Tutorials vermieden auf die objektoriente Programmierung (OOP) einzugehen. Wir
haben die OOP ausgelassen, weil wir davon überzeugt sind, dass es einfacher ist
und mehr Spaß macht, wenn man Python zu lernen beginnt, ohne dass man sich mit
den Details der OOP beschäftigen muss.
Aber auch wenn wir die objektorientierte Programmierung vermieden haben, so war sie dennoch
in unseren Übungen und Beispielen präsent. Wir haben Objekte und Methoden von Klassen benutzt,
ohne eigentlich von ihrer Existenz zu wissen. In diesem Kapitel geben wir nun eine grundlegende
Einführung in den objektorientierten Ansatz von Python. OOP ist eine der mächtigsten
Programmiermöglichkeiten von Python, aber, wie wir gesehen haben, muss man sie dennoch nicht nutzen,
d.h. man kann auch umfangreiche und effiziente Programme ohne OOP-Techniken schreiben.
Auch wenn viele Programmierer und Informatiker die OOP für eine moderne Errungenschaft halten,
so gehen ihre Wurzeln bis in die 1960er-Jahre zurück. Die erste Programmiersprache, die Objekte
verwendete war "Sumula 67" von Ole-Johan Dahl und Kirsten Nygard. Es gibt viele, die davon
überzeugt sind, dass Alles, was nicht objekt orientiert programmiert ist, nichts taugen kann.
Auf der anderen Seite gibt es aber auch zahlreiche anerkannte Experten im Gebiet der
Programmiersprachen, die OOP auch kritisch sehen. So bescheinigte beispielsweise der russische
Informatiker Alexander Stepanow der OOP nur eine eingeschränkte mathematische Sichtweise
und sagte, dass die OOP beinahe ein so großer Schwindel wie die künstliche
Intelligenz sei.1 Alexander Stepanow hat wesentlich an der Entwicklung der "C++
Standard Template Library" mitgewirkt, also sollte er bestens über OOP und ihrer Probleme
in der Praxis Bescheid wissen.
Edsger W. Dijkstra, einer der bekanntesten Professoren der theoretischen Informatik,
schrieb "... was die Gesellschaft mit überwältigender Mehrheit wünscht ist Schlangenöl.
Schlangenöl tritt natürlich mit beeindruckenden Namen auf - ansonsten wäre es unverkäuflich -
so wie "Strukturierte Analyse und Design" ("Structured Analysis and Design"),
"Software Engineering", "Maturity Models" (Reifegradmodell), "Management Information Systems",
"Integrated Project Support Environments" "Object Orientation" und
"Business Process Re-engineering" (letztere sind besser bekannt als IPSE, OO and BPR)."
Das Grundkonzept der objektorientierten Programmierung besteht darin, Daten und deren Funktionen
(Methoden), - d.h. Funktionen, die auf diese Daten angewendet werden können - in einem Objekt
zusammenzufassen und nach außen zu kapseln, so dass Methoden fremder Objekte diese Daten nicht
unmittelbar manipulieren können.
Objekte werden über Klassen definiert.
Eine Klasse ist
eine formale Beschreibung, wie ein Objekt beschaffen ist, d.h. welche Attribute und welche
Methoden sie hat.
Eine Klasse darf nicht mit einem Objekt verwechselt werden. Statt von einem Objekt
spricht man auch von einer Instanz einer Klasse.
Analogie: Kuchenklasse
Bei Einführungen in die objektorientierte Programmierung wird häufig und gerne auf Beispiele aus dem Alltag
zurückgegriffen. Dabei handelt es sich meistens um Beispiele, die zwar helfen, objektorientierte Konzepte zu
verdeutlichen, aber diese Beispiele lassen sich dann nicht in Programmiercode wandeln. So auch in diesem
Beispiel eine Kuchenklassen.
Betrachten wir das Rezept eines Erdbeerkuchens. Dann kann man dieses Rezept im
weitesten Sinne als die Klasse betrachten. Das heißt, das Rezept bestimmt, wie
eine Instanz der Klasse beschaffen sein muss. Backt jemand einen
Kuchen nach diesem Objekt, dann schafft er eine Instanz oder ein Objekt dieser Klasse.
Es gibt dann verschiedene Methoden, diesen Kuchen zu verarbeiten oder zu verändern.
Ein nette Methode stellt übrigens in diesem Beispiel "aufessen" dar.
Ein Erdbeerkuchen gehört in eine übergeordnete Klasse "Kuchen", die ihre Eigenschaften,
z.B. dass ein Kuchen sich als Nachtisch nutzen lässt an Unterklassen wie Erdbeerkuchen,
Rührkuchen, Torten und so weiter vererbt.
Objekte
Der zentrale Begriff in der Objektorientierten Programmierung ist der des Objektes. Ein Objekt bezeichnet in der OOP die Abbildung eines realen Gegenstandes mit seinen Eigenschaften und Verhaltensweisen (Methoden) in ein Programm. Anders ausgedrückt: Ein Objekt kann immer durch zwei Dinge beschrieben werden:
- was es tun kann oder was wir in einem Programm mit ihm tun können
- was wir über es wissen
Klasse
Eine Klasse ist ein abstrakter Oberbegriff für die Beschreibung der gemeinsamen Struktur und des gemeinsamen
Verhaltens von realen Objekten (Klassifizierung).
Reale Objekte werden auf die für die Software wichtigen Merkmale abstrahiert.
Die Klasse dient als Bauplan zur Abbildung von realen Objekten in Softwareobjekte,
die sogenannten Instanzen. Die Klasse fasst hierfür notwendige Eigenschaften (Attribute)
und zur Manipulation der Eigenschaften notwendige Methoden zusammen.
Klassen stehen häufig in Beziehung zueinander. Man hat beispielsweise eine Oberklasse (Kuchen) und aus dieser leitet sich eine andere Klasse ab (Erdbeerkuchen). Diese abgeleitete Klasse erbt bestimmte Eigenschaften und Methoden der Oberklasse.
Methoden und Eigenschaften am Beispiel der Klassen "Kontoinhaber" und "Konto":
Kapselung von Daten
Ein weiterer wesentlicher Vorteil der OOP besteht in der Kapselung von Daten.
Der Zugriff auf die Eigenschaften - also die eigentlichen Daten eines Objektes - sollte möglichst nur über Zugriffsmethoden erfolgen.
Diese Methoden können Plausibilitätstests enthalten und sie (oder "nur" sie)
besitzen "Informationen" über die eigentliche Implementierung der Daten.
So kann z.B. eine Methode zum Setzen des Geburtsdatums prüfen, ob das Datum
korrekt ist und sich innerhalb eines bestimment Rahmens bewegt, z.B. "Girokonto
für Kinder unter 14 nicht möglich" oder "Kunden über 100 Jahre unwahrscheinlich".
Vererbung
In unserem Beispiel erkennt man leicht, dass eine Klasse "Konto" einer realen Bank nicht genügen kann.
Banken bieten üblicherweise verschiedene Arten von Konten:
Girokonto, Sparkonto, usw.
Aber all den unterschiedlichen Konten sind dennoch bestimmte Eigenschaften und Methoden
gemeinsam. So wird wohl zum Beispiel jedes Konto eine Kontonummer, einen Kontoinhaber und einen Kontostand aufweisen. Gemeinsame Methoden für alle Konten sind unter anderem das Einzahlen und und das Auszahlen
Es gibt also so etwas, wie ein Grundkonto, von dem alle anderen Konten "erben".
Die Vererbung dient also dazu, unter Zugrundelegung von existierenden Klassen, neue zu schaffen.
Eine neue Klasse kann dabei sowohl als eine Erweiterung als auch als eine Einschränkung der
ursprünglichen Klasse entstehen.
Klassen in Python
Eine Klasse besteht aus zwei Teilen, dem Kopf und einem Körper.
Der Kopf besteht meist nur aus einer Zeile:
Das Schlüsselwort class, gefolgt von einem Leerzeichen,
einem beliebigen Namen, einer Liste von Oberklassen in Klammern und als
letztes Zeichen ein Doppelpunkt. Hat man keine spezielle Oberklasse, wie
es in unseren anfänglichen Beispielen immer der Fall sein wird, dann entfällt natürlich diese Liste.
Der Doppelpunkt steht dann unmittelbar nach dem Klassennamen oder, - was nicht üblich ist - nach
einem leeren Klammernpaar. Der Körper einer Klasse besteht aus einer eingerückten Folge von
Anweisungen, die beispielsweise auch nur aus einer einzigen pass-Anweisung
bestehen kann.
Damit sind wir bereits in der Lage eine einfache Python-Klasse zu definieren.
Wir nennen diese Klasse Konto, da wir im Folgenden diese Klasse weiter zu einer
"richtigen" Kontoklasse ausbauen werden:
class Konto: passWir haben den Klassennamen "Konto" großgeschrieben, weil es sich dabei um eine typische Namenskonvention handelt und zwar nicht nur in Python sondern auch in anderen Programmiersprachen, wie zum Beispiel in Java. Die Kleinschreibung von Klassennamen ist aber dennoch möglich.
Wir können unsere Klasse auch mit der Angabe von object als Basisklasse definieren, was viele Umsteiger von Python 2 auf Python 3 wohl auch erwartet hatten:
class Konto(object): passSemantisch gibt es hier keinen Unterschied. Die Standardklasse object ist die voreingestellte Basisklasse unabhängig davon, ob man die Klasse ,,Konto'' wie im ersten Beispiel oder wie im zweiten Beispiel definiert. Möchte man Python3-Code gegebenenfalls in älteren Python-Versionen nutzen, -- was generell problematisch ist -- empfiehlt es sich die zweite Variante zu nutzen, da man dann schwere Fehler im alten Code vermeidet.\footnote{In Python 2 stellen die obigen Beispiele unterschiedliche Klassen dar. Im ersten Fall wird eine ,,old-style-Klasse'' und im zweiten Fall eine ,,new-style Klasse'' definiert.}
Anstelle von pass würden bei einer "richtigen" Klasse die Definition der Attribute und Methoden der Klasse stehen. Aber auch wenn diese in unserem Beispiel fehlen, können wir für unsere obige "sinnlose" Klasse bereits Objekte definieren:
>>> class Konto: ... pass ... >>> x = Konto() >>> y = Konto() >>> z = x >>> print(x) <__main__.Konto object at 0xb725a2ec>Wie wir sehen, können wie sogar eine Klasse ausdrucken. Die Ausgabe sagt uns, dass x eine Instanz der KLasse Konto im Namensraum __main__ ist. Die Zahl nach dem at ist ein numerischer Wert, der für die Lebensdauer des Objekte eindeutig und konstant ist. Im Prinzip handelt es sich dabei um die Speicheradresse des Objektes.
Definition von Methoden
Eine Methode unterscheidet sich äußerlich nur in zwei Aspekten von von einer Funktion:
- Sie ist eine Funktion, die innerhalb einer class-Definition definiert ist.
- Der erste Parameter einer Methode ist immer eine Referenz self auf die Instanz, von der sie aufgerufen wird.
Beispiel mit Methode:
class Konto: def ueberweisen(self, ziel, betrag): pass def einzahlen(self, betrag): pass def auszahlen(self, betrag): pass def kontostand(self): pass
Obige Klasse können wir bereits benutzen, auch wenn wir natürlich kaum etwas Sinnvolles damit anstellen können. Wir speichern sie unter dem Namen konto.py und führen folgende Anweisungen in der interaktiven Python-Shell aus:
$ python3 Python 3.2 (r32:88445, Dec 8 2011, 15:26:51) [GCC 4.5.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from konto import Konto >>> x = Konto() >>> x.einzahlen(1000000) >>>In dem Aufruf x.einzahlen(12300000) wird an den formalen Parameter "self" eine Referenz auf die Instanz "x" übergeben.
Konstruktoren
Genaugenommen gibt es in Python keine expliziten Konstruktoren oder Destruktoren.
Häufig wird die __init__-Methode als Konstruktor bezeichnet. Wäre sie wirklich ein Konstruktor,
würde sie wahrscheinlich __constr__ oder __constructor__ heißen. Sie heißt stattdessen __init__, weil
mit dieser Methode ein Objekt, welcher vorher automatisch erzeugt ("konstruiert") worden ist, initialisiert wird.
Diese Methode wird also unmittelbar nach der Konstruktion eines Objektes aufgerufen. Es wirkt also so, als würde das Objekt durch __init__ erzeugt.
Dies erklärt den häufig gemachten Fehler in der Bezeichnungsweise.
Wir benutzen nun die __init__-Methode, um die Objekte unserer Kontoklasse zu initialisieren.
__init__
Konstruktoren werden wie andere Methoden definiert:
def __init__(self, inhaber, kontonummer, kontostand, kontokorrent): self.Inhaber = inhaber self.Kontonummer = kontonummer self.Kontostand = kontostand self.Kontokorrent = kontokorrent
Auch diese Klasse wollen wir in der interaktiven Shell ausprobieren, nachdem wir sie in unsere Kontoklasse als erste Methode eingefügt haben. Wir erkennen, dass wir bei der Definition eines Objektes nun mindestens vier Parameter angeben müssen:
$ python3 Python 3.2 (r32:88445, Dec 8 2011, 15:26:51) [GCC 4.5.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from konto import Konto >>> x = Konto() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() takes at least 4 arguments (1 given) >>> x = Konto("Bill Boe", 234322, 1000) >>>Wir erkennen, dass wir bei der Definition eines Objektes nun mindestens vier Parameter angeben müssen. Versucht man ein Objekt ohne Angabe von Argumenten zu generieren, erhält man eine Fehlermeldung, dass das Objekt mindestens 5 Argumente ("takes exactly 5 arguments") erwarte, aber nur einen ("1 given") erhalten habe. Der scheinbare Widerspruch, dass wir kein Argument übergeben haben, und der Meldung "1 given" kommt daher, dass das Argument "self" automatisch generiert wird und beim Aufruf deshalb nicht angeben wird.
Destruktor
Hier gilt analog das bereits unter dem Absatz Konstrukoren Gesagte: Es gibt keine expliziten Destruktoren.
Für eine Klasse kann man eine Methode __del__ definieren. Wenn man
eine Instanz einer Klasse mit del löscht, wird die Methode __del__ aufgerufen. Allerdings nur, falls es keine weitere Referenz auf diese Instanz
gibt. In C++ werden Destruktoren prinzipiell benötigt, da in ihnen die Speicherbereinigung vorgenommen wird.
Da man sich in Python nicht um die Speicherbereinigung kümmern muss, wird die Methode __del__ relativ selten benutzt.
Im folgenden sehen wir ein Beispiel mit __init__ ("Konstruktor") und __del__ (Destruktor):
class Greeting: def __init__(self, name): self.name = name def __del__(self): print("Destruktor gestartet") def SayHello(self): print("Guten Tag", self.name)Diese Klasse wird nun interaktiv benutzt:
>>> from greeting import Greeting Guten Tag Guido >>> x1 = Greeting("Guido") >>> x2 = x1 >>> del x1 >>> del x2 Destruktor gestartet
Man sollte aber vorsichtig mit der Benutzung der __del__-Methode sein. "del x" startet erst dann die __del--Methode, wenn es keine weiteren Referenzen auf das Objekt gibt, d.h. wenn der Refernzzähler auf 0 gesetzt wird. Probleme gibt es beispielsweise, wenn es zirkuläre Referenzen (Zirkelbezüge), wie beispielsweise bei doppelt verketteten Listen gibt.
Vollständiges Beispiel der Kontoklasse
class Konto: def __init__(self, inhaber, kontonummer, kontostand, kontokorrent=0): self.Inhaber = inhaber self.Kontonummer = kontonummer self.Kontostand = kontostand self.Kontokorrent = kontokorrent def ueberweisen(self, ziel, betrag): if(self.Kontostand - betrag < -self.Kontokorrent): # Deckung nicht genuegend return False else: self.Kontostand -= betrag ziel.Kontostand += betrag return True def einzahlen(self, betrag): self.Kontostand += betrag def auszahlen(self, betrag): self.Kontostand -= betrag def kontostand(self): return self.KontostandSpeichert man obigen Code unter konto.py ab, kann man mit der Kontoklasse in einer Python-Shell wie folgt arbeiten:
$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from konto import Konto >>> konto1 = Konto("Jens",70711,1957.17) >>> konto2 = Konto("Maria",73813,4142.18) >>> konto1.kontostand() 1957.17 >>> konto1.einzahlen(42.83) >>> konto1.kontostand() 2000.0 >>> konto2.kontostand() 4142.18 >>> konto1.ueberweisen(konto2, 857.82) True >>> konto1.kontostand() 1142.1799999999998 >>> konto2.kontostand() 5000.0 >>>
"Öffentlicher" Schönheitsfehler
Leider hat unsere Klasse Konto() noch einen kleinen Schönheitsfehler. Man kann von außen direkt auf die Attribute zugreifen, was natürlich dem Prinzip der Datenkapselung widerspricht:
>>> konto1.Kontostand 1142.1799999999998 >>> konto2.Kontostand 5000.0 >>> konto2.Kontonummer 73813 >>> konto2.Kontostand = 1000000 >>> konto2.Kontostand 1000000Der einfache lesende Zugriff stellt natürlich im obigen Beispiel kein Problem dar, aber wenn jemand den direkt den Kontostand verändert hat das gravierende Folgen für unsere Bank. Selbst wenn der Kontostand korrekterweise wirklich eine Million nach einer Einzahlung betragen sollte, so wären bestimmte andere Größen der Buchführung z.B. der Gesamtbestand aller Konten nicht mehr synchron. Normalerweise müsste ja bei jeder Einzahlung, was wir in unserem Beispiel nicht getan haben, diese Größe auch angepasst werden. Die Methode einzahlen und überweisen müsste auch beispielsweise Massnahmen bzgl. des Geldwäschegesetzes ergreifen, z.B. Warnmeldungen ausgeben, wenn eine Einzahlung über einen bestimmten Betrag geht. All dies wäre ausser Kraft gesetzt, wenn ein direkter Zugriff auf die Attribute möglich ist.
Datenkapselung und die Benutzung von Public- Protected- und Private-Attributen
Normalerweise sind alle Attribute einer Klasseninstanz öffentlich, d.h. von außen zugänglich. Python bietet einen Mechanismus um dies zu verhindern. Die Steuerung erfolgt nicht über irgendwelchen speziellen Schlüsselworte sondern über die Namen, d.h. einfacher dem eigentlichen Namen vorgestellter Unterstrich für den protected und zweifacher vorgestellter Unterstrich für private, wie man der folgenden Tabelle entnehmen kann:
Namen |
Bezeichnung |
Bedeutung |
---|---|---|
name | Public |
Attribute ohne führende
Unterstriche sind sowohl innerhalb einer Klasse als auch von außen
les- und schreibbar. |
_name | Protected |
Man kann zwar auch von
außen lesend und schreibend zugreifen, aber der Entwickler macht damit
klar, dass man diese Member nicht benutzen sollte. |
__name | Private |
Sind von außen nicht
sichtbar und nicht benutzbar. |
Unsere Konto-Beispielklasse sieht mit "private"-Attributen wie folgt aus:
class Konto: def __init__(self, inhaber, kontonummer, kontostand, kontokorrent=0): self.__Inhaber = inhaber self.__Kontonummer = kontonummer self.__Kontostand = kontostand self.__Kontokorrent = kontokorrent def ueberweisen(self, ziel, betrag): if(self.__Kontostand - betrag < -self.__Kontokorrent): # Deckung nicht genuegend return False else: self.__Kontostand -= betrag ziel.__Kontostand += betrag return True def einzahlen(self, betrag): self.__Kontostand += betrag def auszahlen(self, betrag): self.__Kontostand -= betrag def kontostand(self): return self.__KontostandWir testen nun in der interaktiven Python-Shell, ob wir wirklich nicht auf die private-Attribute zugreifen können:
bernd@saturn:~/bodenseo/python/examples$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from konto import Konto >>> konto = Konto("Jens",70711,2013.00) >>> konto.__Kontostand Traceback (most recent call last): File "<stdin>", line 1, inEigentlich würde man die Fehlermeldung erwarten, dass man auf das Attribut __Kontostand nicht zugreifen darf, da es private ist. Stattdessen kommt die obige Meldung, die so tut, als gäbe es kein Attribut __Kontostand in der Klasse Konto.AttributeError: 'Konto' object has no attribute '__Kontostand'
Statische Member
Bisher hatte jedes Objekt einer Klasse seine eigenen Attribute und Methoden,
die sich von denen anderer Objekte unterschieden.
Man bezeichnet dies als "nicht-statisch" oder dynamisch, da sie für jedes
Objekt einer Klasse dynamisch erstellt werden.
Wie kann man aber z.B. die Anzahl der verschiedenen Instanzen/Objekte einer Klasse zählen? In unserer Konto()-Klasse also die Anzahl der verschiedenen Konten.
Statische Attribute werden außerhalb des Konstruktors direkt im class-Block definiert. Es ist Usus die statischen Member direkt unterhalb der class-Anweisung zu positionieren.
In unserem Beispiel der Kontenklasse lässt sich zum Beispiel die
Anzahl der Konten innerhalb des Programms nur statisch zählen, d.h. wir brauchen
eine Klassenvariable, in der wir die Anzahl verwalten:
class Konto: objekt_zaehler = 0 def __init__(self, inhaber, kontonummer, kontostand, kontokorrent=0): self.__Inhaber = inhaber self.__Kontonummer = kontonummer self.__Kontostand = kontostand self.__Kontokorrent = kontokorrent type(self).objekt_zaehler += 1 def __del__(self): type(self).objekt_zaehler -= 1Im Prinzip hätten wir auch Konto.objekt_zaehler statt type(self).objekt_zaehler schreiben können, denn type(self) wird zu "Konto" ausgewertet. Aber wir werden später sehen, dass type(self) bei der Vererbung wesentlich wird.
In der folgenden interaktiven Sitzung können wir verfolgen, wie dieses Zählen vor sich geht:
>>> from class_test import Konto >>> k1 = Konto("Homer Simpson", 2893002, 2325.21) >>> Konto.objekt_zaehler 1 >>> k2 = Konto("Fred Flintstone", 2894117, 755.32) >>> k3 = Konto("Bill Gates", 2895007, 5234.32) >>> Konto.objekt_zaehler 3 >>> k2.objekt_zaehler 3 >>> del k3 >>> k2.objekt_zaehler 2 >>> Konto.objekt_zaehler 2 >>> del k2 >>> Konto.objekt_zaehler 1 >>> del k1 >>> Konto.objekt_zaehler 0 >>>
Allerdings kann es auch einige Probleme mit __del__ geben. Wir demonstrieren dies in folgendem Beispiel:
class C: counter = 0 def __init__(self): C.counter += 1 def __del__(self): C.counter -= 1 if __name__ == "__main__": x = C() print("Anzahl der Instanzen: " + str(C.counter)) y = C() print("Anzahl der Instanzen: " + str(C.counter))Starten wir obiges Programm, erhalten wir folgende Fehlermeldungen:
Anzahl der Instanzen: 1 Anzahl der Instanzen: 2 Exception AttributeError: "'NoneType' object has no attribute 'counter'" in <bound method C.__del__ of <__main__.C object at 0xb721f38c>> ignored Exception AttributeError: "'NoneType' object has no attribute 'counter'" in <bound method C.__del__ of <__main__.C object at 0xb721dc8c>> ignoredDie Fehlermeldungen resultieren daher, dass die Methode \_\_del\_\_ in diesem Fall erst aufgerufen wird, wenn die Klasse bereits gelöscht ist.
Vererbung
So wie man in der Klasse Konto() die Instanzen zählt, so könnte es auch in
anderen Klassen notwendig oder sinnvoll sein. Man möchte aber nicht in jeder
Klasse den Code fürs Hochzählen in den
Konstruktor und den fürs Herunterzählen in den Destruktor übernehmen.
Es gibt die Möglichkeit die Fähigkeit des Instanzen-Zählens an andere Klassen
zu vererben.
Dazu definiert man eine "Ober"klasse Zähler, die ihre Eigenschaften an
andere, wie z.B. Konto überträgt. Im nebenstehenden Diagram gehen wir
beispielsweise davon aus, dass die Klassen "Konto", "Mitglied" und
Angestellter" die Basisklasse "Zähler" benötigen.
Im folgenden zeigen wir den vollständigen Code wir eine solche
Zaehler-Klasse:
class Zaehler: Anzahl = 0 def __init__(self): type(self).Anzahl += 1 def __del__(self): type(self).Anzahl -= 1 class Konto(Zaehler): def __init__(self, inhaber, kontonummer, kontostand, kontokorrent=0): Zaehler.__init__(self)
Mehrfachvererbung
Eine Klasse kann auch Subklasse von mehreren Basisklassen sein, d.h. sie kann
von mehreren Klassen erben. Das Erben von mehreren bezeichnet man
als Mehrfachvererbung
Syntaktisch gesehen ist dies denkbar einfach:
Statt nur eine Klasse innerhalb der KLammer hinter dem Klassennamen gibt man
eine durch Komma getrennte Liste aller Basisklassen an, von denen geerbt
werden soll.
class NN (Klasse1, Klasse2, Klasse3, ...):Die obige Klasse NN erbt also von den Klassen "Klasse1", "KLasse2", "Klasse3" und so weiter.
1 in einem Interview mit Graziano Lo Russo
2 " ... what society overwhelmingly asks for is snake oil. Of course, the snake oil has the most impressive names - otherwise you would be selling nothing - like "Structured Analysis and Design", "Software Engineering", "Maturity Models", "Management Information Systems", "Integrated Project Support Environments" "Object Orientation" and "Business Process Re-engineering" (the latter three being known as IPSE, OO and BPR, respectively)." <