====== Objektorientierte Programmierung in Python ====== Beispiel: Schildkroete import math class Schildkroete: # Konstruktor def __init__(self, zeichenbrett): self.__x = 100 self.__y = 100 self.__winkel = 0 self.__bogenmass = 0 # Methoden (self muss immer der erste Parameter sein) def setX (self, x): self.__x=x def getX (self): return self.__x def setY (self, y): self.__y=y def getY (self): return self.__y def setWinkel (self,w): self.__winkel=w self.__bogenmass = math.pi * 2 * self.__winkel / 360 def getWinkel (self): return this.__winkel def gehe(self, n): dx = n * math.sin (self.__bogenmass) dy = n * math.cos (self.__bogenmass) xNeu = self.__x + dx yNeu = self.__y + dy zeichenbrett.create_line(self.__x, self.__y, xNeu, yNeu) self.__x = xNeu self.__y = yNeu def dreheLinks(self, w): self.__winkel += w + 360 self.__winkel = self.__winkel % 360 self.__bogenmass = math.pi * self.__winkel / 360 * 2 def dreheRechts(self, w): self.__winkel -= w + 360 self.__winkel = self.__winkel % 360 self.__bogenmass = math.pi * self.__winkel / 360 * 2 # Import der Tkinter-Bibliotek, mit deren Hilfe gezeichnet wird from tkinter import * # Fensteranwendung mit Namen fester erstellen fenster = Tk() # Groesse des Fensters breite = 600 hoehe = 600 rand = 5 # erstellt die Komponente , auf der man zeichnen kann zeichenbrett = Canvas (fenster, width=breite, height=hoehe) # zeichnet das Haus des Nikolaus # platziert die erzeugten Elemente (Canvas, Button, Textfelder, ...) im Fenster zeichenbrett.pack() # Endlosschleife: fenster.mainloop() Die objektorientierte Programmierung in Python wird an einem Beispiel demonstriert, das dem Buch "Python" aus dem Verlag Galileo Computing entnommen ist. Ziel ist es, ein sehr einfaches Programm für eine Bank zu entwickeln, mit dessen Hilfe Konten verwaltet sowie Überweisungen und Ein- bzw. Auszahlungen vorgenommen werden können. Dies geschieht mit Objekten der selbst definierten Klasse "''Konto''". ===== Klassen ===== Zur Definition einer Klasse in Python dient das Schlüsselwort ''class'', dem der Name der Klasse folgt. Der Zusatz ''(object)'' wird im Abschnitt "Vererbung" erklärt. class Konto(object): ... ... Ein neues Objekt (also eine Instanz) der Klasse wird durch Angabe des Klassennamens mit einem Klammerpaar erreicht: k1 = Konto() ===== Methoden ===== Die Definition einer Methode unterscheidet sich nur geringfügig von der Definition einer Funktion: Sie steht innerhalb des von ''class'' eingeleiteten Blocks und enthält als ersten Parameter eine Referenz auf die Instanz, über die sie aufgerufen wird. In diesem Beispiel ist das das Objekt selbst, deshalb heißt diese Referenz ''self''. class Konto(object): def ueberweisung(self, ziel, betrag): ... def einzahlen(self, betrag): ... def auszahlen(self, betrag): ... ===== Attribute ===== Attribute sind die Eigenschaften eines Objektes. Im Beispiel der Klasse "Konto" sind die Attribute: Kontoinhaber, Kontonummer, Kontostand, maximaler Tagesumsatz, heutiger Tagesumsatz. ===== Konstruktor ===== Der Konstruktor ist die Methode, die genau einmal bei der Erzeugung des Objektes aufgerufen wird. Da es die Aufgabe des Konstruktors ist, das Objekt in einen definierten Ausgangzustand zu verstetzen, sollten hier auch die Attribute definiert werden. Konstruktoren haben in Python immer den Namen ''__init__'' (zwei Unterstriche vorher und nachher). class Konto(object): def __init__(self, inhaber, kontonummer, kontostand, max_tagesumsatz=1500): self.Inhaber = inhaber self.Kontonummer = kontonummer self.Kontostand = kontostand self.MaxTagesumsatz = max_tagesumsatz self.UmsatzHeute = 0 # hier kommen die restlichen Methoden hin ===== Destruktor ===== Der Destruktor ist die Methode zum Vernichten eines Objektes (in unserem Beispiel: Kontoauflösung). Er hört auf den Namen ''__del__''. class Konto(object): # hier kommt der Konstruktor hin def __del__(self): print "Das Konto wurde aufgelöst" # hier kommen die restlichen Methoden hin ===== Kapselung ===== Attribute und Methoden, die von außen nicht sichtbar sein sollen, können so gekennzeichnet werden, dass nur die Klasse selbst darauf zugreifen kann. Die Manipulation der Objekte erfolgt dann ausschließlich über die von außen sichtbaren Methoden und Attribute.\\ In Python wird über den Namen eines Attributes oder einer Methode festgelegt, ob es von außen sichtbar (public) oder unsichtbar (private) ist. Private Attribute und Methoden beginnen mit zwei Unterstrichen. Das Beispiel wird so geändert, dass die Attribute nicht direkt geändert werden können: class Konto(object): def __init__(self, inhaber, kontonummer, kontostand, max_tagesumsatz=1500): self.__Inhaber = inhaber self.__Kontonummer = kontonummer self.__Kontostand = kontostand self.__MaxTagesumsatz = max_tagesumsatz self.__UmsatzHeute = 0 # hier kommen die restlichen Methoden hin ===== Getter und Setter ===== Sind die Attribute eines Objektes nicht mehr sichtbar, benötigt man von außen zugängliche Methoden, um Attribute auszulesen (Get-Methode oder Getter) oder zu schreiben (Set-Methode oder Setter).\\ In unserem Beispiel wird ein Getter für den Kontostand und das Tageslimit eingefügt. Außerdem schreiben wir einen Setter für das Tageslimit, der gleichzeitig überprüft, ob der übergebene Wert zulässig ist: class Konto(object): # hier kommt der Konstruktor hin # Getter für den Kontostand def kontostand(self): return self.__Kontostand # Getter für das Umsatzlimit def maxTagesumsatz(self): return self.__MaxTagesumsatz # Setter für das Umsatzlimit def setMaxTagesumsatz(self, neues_limit): if neues_limit > 0: self.__MaxTagesumsatz = neues_limit return True else: return False # hier kommen die restlichen Methoden hin ===== Versteckte Getter und Setter ===== In Python ist es möglich, die Get- und Set-Methoden zu "verstecken". Nach außen sieht es so aus, als könnten die Attribute direkt gelesen oder geschrieben werden. Tatsächlich wird aber z.B. bei der Wertzuweisung eine Set-Methode aufgerufen, in der die Werte auf Zulässigkeit geprüft werden.\\ Im folgenden Beispiel wird ''MaxTagesumsatz'' durch den Datentyp ''property'' zum sogenannten //Managed Attribute//, der erste Parameter von ''property'' gibt eine Referenz auf die Getter-Methode, der zweite Parameter auf die Setter-Methode. class Konto(object): # hier kommt der Konstruktor hin # Getter für den Kontostand def kontostand(self): return self.__Kontostand # Getter für das Umsatzlimit def maxTagesumsatz(self): return self.__MaxTagesumsatz # Setter für das Umsatzlimit def setMaxTagesumsatz(self, neues_limit): if neues_limit > 0: self.__MaxTagesumsatz = neues_limit return True else: return False MaxTagesumsatz = property(maxTagesumsatz, setMaxTagesumsatz) # hier kommen die restlichen Methoden hin ===== Statische Member ===== Statische Member, also statische Attribute oder statische Methoden, werden verwendet, um Daten und Funktionen zu erstellen, auf die auch ohne das Erzeugen einer Instanz der Klasse zugegriffen werden kann. Ein statisches Attribut existiert in der Klasse genau einmal, alle Objekte der Klasse teilen sich das Attribut.\\ In Python werden statische Attribute außerhalb des Konstruktors direkt im ''class''-Block definiert. Im folgenden Beispiel wird das statische Attribut ''Anzahl'' definiert, das die Zahl der erzeugten Konten speichert. Dazu wird es mit dem Wert 0 initialisiert und bei jedem Aufruf des Konstruktors um 1 erhöht. Beim Aufruf des Destruktors wird der Wert um 1 reduziert. Statt ''self'' muss eine Referenz auf die Klasse, hier also ''Konto'' vorangestellt werden. class Konto(object): Anzahl = 0 def __init__(self, inhaber, kontonummer, kontostand, max_tagesumsatz=1500): self.__Inhaber = inhaber self.__Kontonummer = kontonummer self.__Kontostand = kontostand self.__MaxTagesumsatz = max_tagesumsatz self.__UmsatzHeute = 0 Konto.Anzahl += 1 def __del__(self): Konto.Anzahl -= 1 # hier kommen die restlichen Methoden hin ===== Überladen ===== Das Überladen von Methoden ist in Python nicht vorgesehen. ===== Vererbung ===== Eine Subklasse erbt die Attribute und Methoden einer Basisklasse, indem die Basisklasse bei der Definition in Klammern angegeben wird. Bei dem Beispiel der Klasse Konto werden durch die Angabe ''class Konto(object)'' die grundlegenden Eigenschaften eines Objektes vererbt. Soll z.B. eine neue Klasse "Bausparvertrag" die Attribute und Methoden der Klasse "Konto" übernehmen, lautet die Definition class Bausparvertrag(Konto): ... ... ===== Überschreiben ===== Wird eine Methode geerbt aber in anderer Weise benötigt, so kann sie überschrieben werden. Überschreiben heißt im Prinzip neu schreiben oder ersetzen. Die Bezeichnung überschreiben ist nur insofern gehaltvoller, als dass wichtig ist, dass der Name der Methode erhalten bleibt.\\ Ist eine Methode in der Subklasse überschrieben worden, so lässt sich die gleichnamige Methode der Basisklasse aufrufen, indem statt ''self'' der Klassenname vorangestellt wird. ===== Mehrfachvererbung ===== In Python ist es möglich, dass eine Subklasse die Attribute und Methoden von mehreren Basisklassen erbt. Existieren beispielsweise die Klassen "Haus" und "Boot", so könnte eine Klasse "Hausboot" definiert werden, die alle Attribute und Methoden eines Hauses und alle Attribute und Methoden eines Bootes erbt. (In Java gibt es die Möglichkeit der Mehrfachvererbung nicht, da die Mehrfachvererbung eine häufige Fehlerquelle darstellt.) ===== Polymorphie ===== Polymorphie bedeutet, dass eine Methode mit gleichem Namen in verschiedenen Klassen existiert. Welche Methode für ein gegebenes Objekt benutzt wird, wird erst zur Laufzeit festgelegt, denn dies ist abhängig davon, welcher Klasse das Objekt dann angehört. ===== Beispiel ===== ==== Erläuterung ==== Als Beispiel dient wieder die einfache Bank-Software, die um einige Funktionen erweitert wird, um möglichst viele der oben angesprochenen Punkte umzusetzen. Das Programm selber macht noch nichts, sondern legt nur die Klassen mit ihren Attributen und Methoden fest. Die Erzeugung von Objekten und das Aufrufen der Methoden geschieht von der Kommandozeile aus. Folgende Punkte sollen implementiert werden: * Die oben schon benutzte Klasse "Konto" soll eine Subklasse von "Zaehler" sein. Die Klasse "Zaehler" zählt die vorhandenen Instanzen, kann also angeben, wie viele Konten existieren. Dies geschieht mit einem statischen Attribut. * Die Klasse "Konto" bekommt einen versteckten Getter und Setter für den maximalen Tagesumsatz. * Es wird eine weitere Klasse "Angestellte" als Erbe von "Zaehler" definiert. Damit können nun nicht nur die Konten, sondern nun auch die Angestellten der Bank verwaltet und gezählt werden. * Die zwei weiteren Klassen "Sekretaerin" und "Bankdirektor" erben die Attribute und Methoden von "Angestellte" und erweitern diese. ==== Python-Code ==== class Zaehler(object): Anzahl = 0 def __init__(self): type(self).Anzahl += 1 def __del__(self): type(self).Anzahl -= 1 class Konto(Zaehler): # Konstruktor für Konto def __init__(self, inhaber, kontonummer, kontostand, max_tagesumsatz=1500): Zaehler.__init__(self) self.__Inhaber = inhaber self.__Kontonummer = kontonummer self.__Kontostand = kontostand self.__MaxTagesumsatz = max_tagesumsatz self.__UmsatzHeute = 0 print "Es wurde ein neues Konto eröffnet!" # Destruktor für Konto def __del__(self): Zaehler.__del__(self) print "Das Konto wurde aufgelöst!" # Betrag auf Konto einzahlen def einzahlen(self, betrag): if self.__UmsatzHeute + betrag <= self.__MaxTagesumsatz: self.__Kontostand += betrag self.__UmsatzHeute += betrag print "Der gewünschte Betrag wurde eingezahlt." else: print "Eine Einzahlung ist leider nicht möglich!" # Kontodaten anzeigen def anzeigen(self): print "Kontoinhaber: ",self.__Inhaber print "Kontonummer: ",self.__Kontonummer print "Kontostand: %.2f Euro"%(self.__Kontostand) print "Maximaler Tagesumsatz: %.2f Euro"%(self.__MaxTagesumsatz) print "Heutiger Umsatz: %.2f Euro"%(self.__UmsatzHeute) # Getter für das Umsatzlimit def maxTagesumsatz(self): return self.__MaxTagesumsatz # Setter für das Umsatzlimit def setMaxTagesumsatz(self, neues_limit): if type(neues_limit) in (float, int) and neues_limit > 0: self.__MaxTagesumsatz = neues_limit print "Das Limit wurde geändert!" else: print "Unzulässiger Wert! Das Limit wurde nicht geändert." # Managed Attribute mit verstecktem Getter und Setter MaxTagesumsatz = property(maxTagesumsatz, setMaxTagesumsatz) class Angestellter(Zaehler): # Konstruktor für Angestellte def __init__(self, name, stundenlohn, stunden_pro_woche): Zaehler.__init__(self) self.Name = name self.Stundenlohn = stundenlohn self.StundenProWoche = stunden_pro_woche # Personaldaten anzeigen def anzeigen(self): print "Name: ",self.Name print "Stundenlohn: ",self.Stundenlohn print "Wochenstunden:",self.StundenProWoche class Sekretaerin(Angestellter): # Konstruktor für Sekretärin def __init__(self, name): Angestellter.__init__(self, name, 15, 30) class Bankdirektor(Angestellter): # Konstruktor für Bankdirektor def __init__(self, name, dienstwagen): Angestellter.__init__(self, name, 150, 50) self.Dienstwagen = dienstwagen # Personaldaten anzeigen (Methode überschreiben) def anzeigen(self): Angestellter.anzeigen(self) print "Dienstwagen: ",self.Dienstwagen ==== Ausprobieren im interaktiven Modus ==== Konten einrichten und auflösen, zählen der vorhandenen Konten: >>> Konto.Anzahl 0 >>> k1=Konto("Max",1234,100) Es wurde ein neues Konto eröffnet! >>> k2=Konto("Moritz",5678,249.90,1000) Es wurde ein neues Konto eröffnet! >>> Konto.Anzahl 2 >>> k1.Anzahl 2 >>> del k1 Das Konto wurde aufgelöst! >>> k1.Anzahl Traceback (most recent call last): File "", line 1, in k1.Anzahl NameError: name 'k1' is not defined >>> Konto.Anzahl 1 >>> del k2 Das Konto wurde aufgelöst! >>> Konto.Anzahl 0 Anzeigen und einzahlen unter Berücksichtigung des Tagesumsatzes: >>> k1=Konto("Bibbi Blocksberg",1080965,2745.2,1000) Es wurde ein neues Konto eröffnet! >>> k1.anzeigen > >>> k1.anzeigen() Kontoinhaber: Bibbi Blocksberg Kontonummer: 1080965 Kontostand: 2745.20 Euro Maximaler Tagesumsatz: 1000.00 Euro Heutiger Umsatz: 0.00 Euro >>> k1.einzahlen(800) Der gewünschte Betrag wurde eingezahlt. >>> k1.anzeigen() Kontoinhaber: Bibbi Blocksberg Kontonummer: 1080965 Kontostand: 3545.20 Euro Maximaler Tagesumsatz: 1000.00 Euro Heutiger Umsatz: 800.00 Euro >>> k1.einzahlen(300) Eine Einzahlung ist leider nicht möglich! >>> k1.anzeigen() Kontoinhaber: Bibbi Blocksberg Kontonummer: 1080965 Kontostand: 3545.20 Euro Maximaler Tagesumsatz: 1000.00 Euro Heutiger Umsatz: 800.00 Euro Benutzung des verwalteten Attributes "MaxTagesumsatz", das die versteckte Get- und Set-Methode aufruft: >>> k1=Konto("Donald Duck",246810,120.55,200) Es wurde ein neues Konto eröffnet! >>> k1.MaxTagesumsatz 200 >>> k1.MaxTagesumsatz = 400 Das Limit wurde geändert! >>> k1.MaxTagesumsatz = -2 Unzulässiger Wert! Das Limit wurde nicht geändert. >>> k1.MaxTagesumsatz = "Unsinn" Unzulässiger Wert! Das Limit wurde nicht geändert. >>> k1.anzeigen() Kontoinhaber: Donald Duck Kontonummer: 246810 Kontostand: 120.55 Euro Maximaler Tagesumsatz: 400.00 Euro Heutiger Umsatz: 0.00 Euro Definieren einiger Objekte des Typs "Angestellter", ausführen der geerbten und überschriebenen Methode "anzeigen" >>> a=Angestellter("Hans Wurst",20,40) >>> s=Sekretaerin("Bibbi Bleistift") >>> b=Bankdirektor("Siggi Superreich","Cadillac") >>> a.anzeigen() Name: Hans Wurst Stundenlohn: 20 Wochenstunden: 40 >>> s.anzeigen() Name: Bibbi Bleistift Stundenlohn: 15 Wochenstunden: 30 >>> b.anzeigen() Name: Siggi Superreich Stundenlohn: 150 Wochenstunden: 50 Dienstwagen: Cadillac Zählen der Objekte. Beachte: Eine Sekretärin wird nicht als "Angestellter" gezählt, ebenso wenig wie der Bankdirektor. >>> k1=Konto("Max",123,100) Es wurde ein neues Konto eröffnet! >>> k2=Konto("Moritz",456,100) Es wurde ein neues Konto eröffnet! >>> a=Angestellter("Hans",20,40) >>> b=Bankdirektor("Siggi","Fiat") >>> s1=Sekretaerin("Bibbi") >>> s2=Sekretaerin("Lilli") >>> Konto.Anzahl;Angestellter.Anzahl;Sekretaerin.Anzahl;Bankdirektor.Anzahl 2 1 2 1 >>> del a >>> Konto.Anzahl;Angestellter.Anzahl;Sekretaerin.Anzahl;Bankdirektor.Anzahl 2 0 2 1 >>> del s2 >>> Konto.Anzahl;Angestellter.Anzahl;Sekretaerin.Anzahl;Bankdirektor.Anzahl 2 0 1 1