Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by haas.homelinux.net...

Inhaltsverzeichnis
1 Einleitung
2 Überblick über Python
3 Die Arbeit mit Python
4 Der interaktive Modus
5 Grundlegendes zu Python-Programmen
6 Kontrollstrukturen
7 Das Laufzeitmodell
8 Basisdatentypen
9 Benutzerinteraktion und Dateizugriff
10 Funktionen
11 Modularisierung
12 Objektorientierung
13 Weitere Spracheigenschaften
14 Mathematik
15 Strings
16 Datum und Zeit
17 Schnittstelle zum Betriebssystem
18 Parallele Programmierung
19 Datenspeicherung
20 Netzwerkkommunikation
21 Debugging
22 Distribution von Python-Projekten
23 Optimierung
24 Grafische Benutzeroberflächen
25 Python als serverseitige Programmiersprache im WWW mit Django
26 Anbindung an andere Programmiersprachen
27 Insiderwissen
28 Zukunft von Python
A Anhang
Stichwort

Download:
- ZIP, ca. 4,8 MB
Buch bestellen
Ihre Meinung?

Spacer
 <<   zurück
Python von Peter Kaiser, Johannes Ernesti
Das umfassende Handbuch - Aktuell zu Python 2.5
Buch: Python

Python
gebunden, mit CD
819 S., 39,90 Euro
Galileo Computing
ISBN 978-3-8362-1110-9
Pfeil 12 Objektorientierung
  Pfeil 12.1 Klassen
    Pfeil 12.1.1 Definieren von Methoden
    Pfeil 12.1.2 Konstruktor, Destruktor und die Erzeugung von Attributen
    Pfeil 12.1.3 Private Member
    Pfeil 12.1.4 Versteckte Setter und Getter
    Pfeil 12.1.5 Statische Member
  Pfeil 12.2 Vererbung
    Pfeil 12.2.1 Mehrfachvererbung
  Pfeil 12.3 Magic Members
    Pfeil 12.3.1 Allgemeine Magic Members
    Pfeil 12.3.2 Datentypen emulieren
  Pfeil 12.4 Objektphilosophie


Galileo Computing - Zum Seitenanfang

12.2 Vererbung  Zur nächsten ÜberschriftZur vorigen Überschrift

Bisher haben wir nur objektorientierte Techniken behandelt, die durch Kapselung von Daten und Definition von Schnittstellen die Konsistenz der Objekte sichern. Eines der zu Anfang des Kapitels angesprochenen Ziele der Objektorientierung war es aber auch, dass unsere Programme auch leicht veränderlich sind, sodass sie auf Probleme angewandt werden können, die dem ursprünglichen Problem ähnlich sind. Dieses Ziel wird aber mit den bis jetzt eingeführten Techniken noch nicht erreicht.

Wir haben im letzten Abschnitt unsere Klasse Konto so erweitert, dass sie mittels eines statischen Attributs die Anzahl ihrer Instanzen nachhalten konnte. Wenn wir nun eine neue Klasse definieren wollten – nehmen wir beispielhaft eine Klasse, die Angestellte der Bank beschreibt – und diese ebenfalls die Anzahl ihrer eigenen Instanzen – in dem Fall also die Zahl der Angestellten – ermitteln soll, so müssten wir den Quellcode für das Instanzzählen ein weiteres Mal in die Klasse Angestellter schreiben. Es wäre wünschenswert, einmal festzulegen, wie eine Klasse ihre eigenen Instanzen zählt, und diese Fähigkeit ohne erneutes Aufschreiben des Codes auf neue Klassen übertragen zu können.

Dieses Konzept, Fähigkeiten einer Klasse auf eine andere zu übertragen, nennt man Vererbung, wobei alle Member, also sowohl Attribute als auch Methoden, von der Mutter- auf die Tochterklasse übertragen werden. In unserem Beispiel hätten wir also eine Mutterklasse Zaehler, die die Instanzzählung implementiert und von der die Klassen Konto und Angestellter diese Fähigkeit erben:

Abbildung 12.2  Konto und Angestellter erben von Zaehler.

Man spricht auch davon, dass die Basisklasse Zaehler ihre Member an die beiden Subklassen, Konto und Angestellter, vererbt.

Wir wollen nun das angegebene Beispiel in Python implementieren, wobei wir uns zuerst der Zaehler-Klasse zuwenden:

class Zaehler(object): 
    Anzahl = 0 
 
    def __init__(self): 
        type(self).Anzahl += 1 
 
    def __del__(self): 
        type(self).Anzahl -= 1

Die Definition enthält bis auf den Zugriff auf das Attribut Anzahl mittels type(self) nichts Neues. Wir können deshalb nicht mehr direkt über den Klassennamen per Zaehler.Anzahl auf das Attribut zugreifen, weil wir von der Klasse erben wollen und die Subklassen jeweils ihr eigenes statisches Attribut Anzahl haben sollen. Würden wir mit Zaehler.Anzahl arbeiten, könnten wir damit die Gesamtanzahl der Konto- und Angestellter-Instanzen berechnen. Mithilfe von type lässt sich der Datentyp einer Instanz ermitteln, und das nutzen wir, um den Zähler abhängig davon, welchen Typ self hat, für die richtige Klasse zu ändern.

Um nun unsere Klasse Konto von Zaehler erben zu lassen, müssen wir anstatt des object innerhalb der Klammern hinter dem Klassennamen Zaehler verwenden. Tatsächlich ist es so, dass wir bis hierher alle unsere Klassen von der Basisklasse object haben erben lassen, wodurch sie grundlegende Eigenschaften erhalten haben, damit sie überhaupt als Klasse nutzbar wurden.

Der Grund dafür, dass diese Basisklasse explizit angegeben werden muss, ist historisch bedingt. Früher wurde in Python streng zwischen eingebauten Datentypen und Klassen unterschieden, sodass es insbesondere nicht möglich war, eigene Klassen von diesen Datentypen erben zu lassen. Als man erkannte, dass dies ein großer Nachteil war, vereinigte man eingebaute Datentypen und selbstdefinierte Klassen. Allerdings führte dieser Schritt unter bestimmten Bedingungen zu Problemen mit Programmen, die noch die »alten« Klassen verwendeten. Deshalb kann man heute durch das Erben von object explizit angeben, dass man eine »neue« Klasse definieren möchte. Da die überholten old-style classes gegenüber den von object abgeleiteten new-style classes nur Nachteile haben, sollten Sie ausschließlich mit Letzteren arbeiten.

Nun wollen wir unsere Konto-Klasse von Zaehler erben lassen:

class Konto(Zaehler): 
    def __init__(self, inhaber, kontonummer, kontostand, 
                       max_tagesumsatz=1500):
Zaehler.__init__(self) # Wichtige Zeile - siehe unten
self.__Inhaber = inhaber self.__Kontonummer = kontonummer self.__Kontostand = kontostand self.__MaxTagesumsatz = max_tagesumsatz self.__UmsatzHeute = 0 # hier wären die restlichen Methoden

Im Wesentlichen haben sich bei der neuen Definition von Konto nur das schon angesprochene Ersetzen von object durch Zaehler und die erste Zeile des Konstruktors geändert. Mit Zaehler.__init__(self) rufen wir den Konstruktor der Basisklasse auf, um unser Konto auch als Zähler benutzen zu können. Dies ist deshalb notwendig, weil eine Klasse nur eine Methode __init__ haben kann. Bei der Vererbung tritt nun oft der Fall ein, dass die erbende Klasse Methoden definiert, die auch schon in der Basisklasse vorhanden waren – in unserem Beispiel eben der Konstruktor __init__. In einem solchen Fall werden die Methoden der Basisklasse mit denen, die die Subklasse selbst definiert, überschrieben, sodass im Beispiel self.__init__ eine Referenz auf den Konstruktor von Konto und nicht auf den von Zaehler enthält. Um trotzdem auf solche überschriebenen Methoden zugreifen zu können, ersetzt man beim Aufruf das self vor dem Punkt durch den Namen der entsprechenden Basisklasse und übergibt self explizit als Parameter. Würde Zaehler.__init__ noch weitere Parameter erwarten, so würden diese wie üblich durch Kommata getrennt dahinter geschrieben.

Sie sollten sich außerdem als wichtige Regel merken, dass Sie im Konstruktor einer abgeleiteten Klasse immer den Konstruktor der Basisklasse aufrufen müssen, weil Ihre Instanzen sonst aufgrund der fehlenden Initialisierung in einen nicht definierten Zustand übergehen oder sich auf andere Weise falsch verhalten können.

In unserem Fall würde die Instanzzählung ohne den Aufruf des Konstruktors der Basisklasse nicht funktionieren, da der Zähler nicht mit 0 initialisiert würde.

Natürlich können Sie von einer erbenden Klasse weitere Klassen erben lassen, sodass ganze »Stammbäume« entstehen. Wenn Sie beispielsweise bei der Speicherung der Bankangestellten eigene Klassen für jeden Tätigkeitsbereich definieren möchten, so könnten diese von der Klasse Angestellter erben, die wiederum Zaehler als Basisklasse hat:

class Angestellter(Zaehler): 
    def __init__(self, name, stundenlohn, stunden_pro_woche): 
        Zaehler.__init__(self) 
        self.Name = name 
        self.Stundenlohn = stundenlohn 
        self.StundenProWoche = stunden_pro_woche 
 
    def befoerdere(self, neue_position): 
        # hier würde der Code für eine Beförderung stehen 
        pass

Unsere Angestellten haben der Einfachheit halber nur ihren Namen, ihren Stundenlohn und ihre durchschnittliche Arbeitszeit pro Woche in Stunden als Attribute. Nun könnten wir die beiden speziellen Angestellten, Sekretaerin und Bankdirektor, definieren, die jeweils von der Klasse Angestellter erben:

class Sekretaerin(Angestellter): 
    def __init__(self, name): 
        Angestellter.__init__(self, name, 15, 30) 
 
class Bankdirektor(Angestellter): 
    def __init__(self, name, dienstwagen): 
        Angestellter.__init__(self, name, 150, 50) 
        self.Dienstwagen = dienstwagen

Da es in unserer Bank Standardarbeitszeiten und einheitliche Gehälter für jede Position gibt, brauchen diese Informationen nicht mehr an den Konstruktor der abgeleiteten Klassen übergeben zu werden, sondern werden bei dem Aufruf des Konstruktors der Basisklasse intern weitergegeben. Die Sekretaerin hat in unserem einfachen Beispiel neben den von Angestellter geerbten Members keine weiteren Attribute oder Methoden, und der Bankdirektor bekommt neben dem »Erbgut« nur noch ein neues Attribut für seinen Dienstwagen dazu.

Mithilfe des Konzepts der Vererbung wird Ihr Programmtext in hohem Maße wiederverwendbar, vorausgesetzt, Sie machen sich bei der Strukturierung Ihrer Programme entsprechende Gedanken und zerlegen sie in sinnvoll aufgeteilte Klassen.


Galileo Computing - Zum Seitenanfang

12.2.1 Mehrfachvererbung  topZur vorigen Überschrift

Bisher haben wir eine Subklasse immer von genau einer Basisklasse erben lassen. Es gibt aber Situationen, in denen eine Klasse die Fähigkeiten von zwei oder noch mehr Basisklassen erben soll, um das gewünschte Ergebnis zu erzielen. Dieses Konzept, bei dem eine Klasse von mehreren Basisklassen erbt, wird Mehrfachvererbung genannt.

Möchte man eine Klasse von mehreren Basisklassen erben lassen, muss man die Basisklassen durch Kommata getrennt in die Klammern hinter dem Klassennamen schreiben:

class NeueKlasse(Basisklasse1, Basisklasse2, Basisklasse3, ...): 
    # Definition von Methoden und Attributen 
    pass

Wir werden die Mehrfachvererbung an einem einfachen Beispiel verdeutlichen. Angenommen, wir möchten eine Klasse für die Beschreibung von Hausbooten entwickeln, so könnten wir einfach jeweils eine Klasse für die Beschreibung eines Hauses und eine für die eines Bootes definieren, sodass wir durch Vererbung Spezialformen wie das Ferienhaus oder das Rennboot von jeweils einer der Klassen erben lassen könnten. [Dieses Beispiel ist zugegebenermaßen relativ praxisfern, eignet sich aber trotzdem gut, um das Konzept der Mehrfachvererbung zu veranschaulichen. ]

Unsere Hausbootklasse soll die Eigenschaften von beiden Klassen, Haus und Boot erben.

Die beiden Klassen für das Haus und das Boot könnten in stark vereinfachter Form folgendermaßen aussehen:

class Haus(object): 
    def __init__(self, anzahl_stockwerke, anzahl_zimmer, 
                       flaeche, hausnummer): 
        self.AnzahlStockwerke = anzahl_stockwerke 
        self.AnzahlZimmer = anzahl_zimmer 
        self.Flache = flaeche 
        self.Hausnummer = hausnummer 
 
        self.HaustuerOffen = False 
 
    def oeffneHaustuer(self): 
        self.HaustuerOffen = True 
 
    def schliesseHaustuer(self): 
        self.HaustuerOffen = False 
 
class Boot(object): 
    def __init__(self, laenge, tiefgang, motorleistung): 
        self.Laenge = laenge 
        self.Tiefgang = tiefgang 
        self.Motorleistung = motorleistung 
 
        self.MotorIstEingeschaltet = False 
        self.AnkerGeworfen = True 
 
    def starteMotor(self): 
        self.MotorIstEingeschaltet = True 
 
    def stoppeMotor(self): 
        self.MotorIstEingeschaltet = False 
 
    def ankerWerfen(self): 
        self.AnkerGeworfen = True 
 
    def ankerLichten(self): 
        self.AnkerGeworfen = False

Die Klasse Haus kann sich einige grundlegende Eigenschaften eines Hauses merken und außerdem speichern, ob die Haustür gerade offen bzw. geschlossen ist. Außerdem bietet sie zum Öffnen und Schließen der Türe entsprechende Methoden an.

Mit der Klasse Boot kann man die Länge, den Tiefgang und die Motorleistung in PS speichern. Sie verfügt zusätzlich über Eigenschaften für den Status des Motors und des Ankers, die auch jeweils über Methoden gesetzt werden können.

Nun lassen wir unsere neue Klasse namens Hausboot von den Klassen Haus und Boot erben, wodurch sie alle Fähigkeiten von ihnen übernimmt. Da wir keine zusätzliche Funktionalität hinzufügen wollen, definieren wir nur einen Konstruktor für die Klasse Hausboot, der die Parameter an die Konstruktoren von Haus und Boot weitergibt:

class Hausboot(Haus, Boot): 
    def __init__(self, anzahl_stockwerke, anzahl_zimmer, 
                       flaeche, hausnummer, 
                       laenge, tiefgang, motorleistung): 
 
        Haus.__init__(self, anzahl_stockwerke, anzahl_zimmer, 
                      flaeche, hausnummer) 
        Boot.__init__(self, laenge, tiefgang, motorleistung)

Nun können wir eine Instanz der Klasse Hausboot erzeugen und zur Demonstration den Anker werfen und den Motor starten:

>>> mein_hausboot = Hausboot(2, 10, 200, 5, 20, 1.5, 1000) 
>>> mein_hausboot.AnzahlStockwerke 
2 
>>> mein_hausboot.starteMotor() 
>>> mein_hausboot.MotorGestartet 
True 
>>> mein_hausboot.AnkerGeworfen 
False 
>>> mein_hausboot.werfeAnker() 
>>> mein_hausboot.Ankergeworfen 
True

Wie das Beispiel zeigt, können wir die Instanz mein_hausboot problemlos wie ein Haus und wie ein Boot verwenden.

Mehrfachvererbung wird erst dann kniffelig, wenn einer Klasse gleichnamige Attribute oder Methoden von verschiedenen Basisklassen vererbt werden.

Was wäre beispielsweise passiert, wenn die Klasse Hausboot keinen eigenen Konstruktor definiert hätte, der die Konstruktoren beider Basisklassen aufruft? Wäre der Konstruktor der Basisklasse Haus oder der der Klasse Boot oder wären vielleicht beide aufgerufen worden?

Wenn in Python eine Klasse von mehreren Basisklassen gleichnamige Member erbt, wird nach der Reihenfolge entschieden, in der die Basisklassen angegeben werden: Es werden immer zuerst die Eigenschaften der weiter links stehenden Basisklasse vererbt.

Wenn wir also eine Klasse Hausboot2 definieren, die ebenfalls von Haus und Boot erbt und deren Klassenkörper ausschließlich aus einer pass-Anweisung besteht, würde Hausboot2 die __init__-Methode von Haus erben:

 class Hausboot2(Haus, Boot): 
    pass
>>> mein_hausboot2 = Hausboot2() Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> mein_hausboot2 = Hausboot2() TypeError: __init__() takes exactly 5 arguments (1 given)

Die Fehlermeldung teilt uns mit, dass der Konstruktor von Hausboot2 genau fünf Parameter erwartet, was genau der Parameteranzahl des Konstruktors von Haus entspricht. Da die __init__-Methode von Boot nur vier Parameter benötigt, handelt es sich beim Konstruktor von Hausboot2 also um den der Haus-Klasse.

Mögliche Probleme der Mehrfachvererbung

Es ist kein Zufall, dass nur wenige Sprachen das Konzept der Mehrfachvererbung unterstützen, da Programme, die es verwenden, anfällig für schwierig auffindbare Fehler sind, weil gleichnamige Member auch dann überschrieben werden, wenn sie semantisch nichts miteinander zu tun haben.

Besonders kritisch wird es dann, wenn eine Klasse über Umwege mehrmals von derselben Basisklasse erbt. Betrachten wir einmal folgende vereinfachte Klassenhierarchie:

Abbildung 12.3  Amphibienfahrzeug erbt auf zwei Wegen von Fahrzeug

Die Klasse Amphibienfahrzeug hat exakt ein Attribut Maximalgeschwindigkeit, das sie entweder von Geländefahrzeug oder von Wasserfahrzeug erbt, je nachdem, in welche Reihenfolge die beiden Basisklassen bei der Definition von Amphibienfahrzeug angegeben wurden. Dies ist aber nicht sinnvoll, da sich die Maximalgeschwindkeiten zu Lande und zu Wasser in der Regel unterscheiden. Eine brauchbare Klasse zur Beschreibung von Amphibienfahrzeugen lässt sich also nicht durch die gezeigte Mehrfachvererbung definieren, wie es die Intuition raten würde.

Sie sollten in Ihren eigenen Programmen sehr genau darauf achten, dass Sie nur dann Mehrfachvererbungen einsetzen, wenn dadurch keine Konflikte entstehen können, die den Sinn der resultierenden Klasse entstellen – und nach Möglichkeit ganz auf sie verzichten.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






 <<   zurück
  
  Zum Katalog
Zum Katalog: Python






Python
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Linux






 Linux


Zum Katalog: Ubuntu GNU/Linux






 Ubuntu GNU/Linux


Zum Katalog: Praxisbuch Web 2.0






 Praxisbuch Web 2.0


Zum Katalog: UML 2.0






 UML 2.0


Zum Katalog: Praxisbuch Objektorientierung






 Praxisbuch Objektorientierung


Zum Katalog: Einstieg in SQL






 Einstieg in SQL


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für Fachinformatiker


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo