Klassen

objektorientierte Programmierung

Veranschaulichung von Klassen: Obst als Klasse 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

Obst als Klasse 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:

Objekte sind Instanzen oder Exemplare einer Klasse. Die Begriffe Objekt und Instanz werden meist synonym gebraucht und bezeichnen den gleichen "Gegenstand". Objekte oder Instanzen werden mittels Konstruktoren erzeugt. Konstruktoren sind spezielle Methoden zur Erzeugung von Instanzen einer Klasse. Zum Entfernen oder Löschen von Instanzen gibt es die Destruktor-Methode.

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":
Eigenschaften und Methoden

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". Kapselung von Daten

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".

Vererbung

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:
	pass
Wir 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):
        pass
Semantisch 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:

Der Parameter self erscheint nur bei der Definition einer Methode. Beim Aufruf wird er nicht angegeben.
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.Kontostand
Speichert 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
1000000
Der 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.__Kontostand
Wir 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, in 
AttributeError: 'Konto' object has no attribute '__Kontostand'
Eigentlich 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.


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 -= 1
Im 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>> ignored
Die Fehlermeldungen resultieren daher, dass die Methode \_\_del\_\_ in diesem Fall erst aufgerufen wird, wenn die Klasse bereits gelöscht ist.


Vererbung

Vererbung am Beispiel der
						     Zaehler-KlasseSo 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)." <