.. _kapKlassen: ################### Klassen ################### Python unterstützt auch die objektorientierte Programmierung. Es können Klassen definiert werden, die Attribute und Methoden kapseln. Durch die gemeinsame Definition von Attributen und Methoden innerhalb des Namensraums einer Klasse wird Modularisierung und Zugriffskontrolle erreicht. Da die Klassenarchitektur möglichst an die reale Welt angepasst sein sollte (d.h. was in der realen Welt zusammengehört, sollte auch innerhalb einer Klasse modelliert werden) wird die Verständlichkeit des Codes gegenüber anderen Programmieransätzen wie z.B. der prozeduralen Programmierung deutlich verbessert. Wie in anderen Programmiersprachen, definieren auch in Python Klassen nur einen abstrakten Bauplan. Konkrete Realisierungen von Klassen werden Objekte, oder Instanzen genannt. Da von einer Klasse beliebig viele Instanzen erzeugt werden können, wird mit der Objektorientierung auch der Vorteil des Code-Reuse erreicht. Jedes Objekt einer Klasse bildet einen eigenen Namensraum (abgesehen von den statischen Attributen). Attribute und Methoden können von aussen zugänglich (public) oder nicht zugänglich (privat) sein. Python unterstützt auch das Konzept der Vererbung, welches sowohl Code-Reuse als auch Verständlichkeit fördert. Im Gegensatz zu Java ist in Python Mehrfachvererbung möglich. Ein weiterer Unterschied zu Java ist, dass in Python alles Objekt ist, d.h. es gibt keine primitve Typen, selbst ganze Zahlen, oder boolsche Variablen sind in Python Objekte. Definition von Klassen ================================================= In Python wird eine Klasse innerhalb eines Files definiert. Dieses File kann auch mehrere Klassen oder ausserhalb von Klassen definierte Variablen und Funktionen enthalten. Derartige Files werden in Python :ref:`Module` genannt und müssen die Extension ``.py`` aufweisen. Die Definition einer Klasse beginnt in Python mit einer Kopfzeile der Form:: class meineKlasse: Dem Schlüsselwort ``class`` folgt der Name der Klasse. Nach dem Klassennamen folgt im Fall, dass die Klasse von keiner anderen Klasse abgeleitet wird der Doppelpunkt. Alle folgenden Anweisungen, die den Rumpf der Klassendefinition bilden, müssen wie in Python üblich eingerückt werden. Die Definition einer Klasse kann jedoch nicht nur aus der Kopfzeile bestehen (Syntaxfehler). Während des Entwicklungsprozess kann es vorkommen, dass man zunächst nur den Kopf einer Klasse definieren möchte, die Definition des Klassenrumpfs jedoch erst später schreiben möchte. Um in diesem Fall einen Syntaxfehler zu vermeiden, ist in Python das Schlüsselwort ``pass`` vorgesehen. Dieses Schlüsselwort kann überall dort eingefügt werden wo vorübergehend noch kein Anweisungsblock geschrieben werden soll, ein solcher aber für die syntaktische Korrektheit notwendig wäre. Folgender Programmcode erzeugt also keinen Syntaxfehler:: class meineKlasse: pass Das Schlüsselwort ``pass`` kann auch als vorübergehender Rumpf einer Funktionsdefinition oder als vorübergehender Rumpf einer Kontrollstruktur (z.B. if-then-else Anweisung) benutzt werden. Methoden ------------------------- Methoden sind Funktionen, die innerhalb einer Klasse definiert werden. Sie sind innerhalb der Klasse sichtbar und benutzbar. Von aussen kann auf Methoden nur über eine Referenz auf ein Objekt dieser Klasse zugegriffen werden und dies auch nur dann, wenn die Methode nicht als ``privat`` deklariert wird. Eine weitere Besonderheit von Methoden ist, dass das erste Element innerhalb der Parameterliste immer ``self`` sein muss. Über den Parameter ``self`` erhält die Methode beim Aufruf eine Referenz auf das Objekt für welches sie aufgerufen wird. Folgender Code definiert 2 Methoden innerhalb einer Klasse. Der Rumpf der Methoden wird vorerst noch nicht definiert:: class meineKlasse: def methode1(self,par1,par2): pass def methode2(self,par1='a',par2=3): pass Attribute --------------- Wie in anderen Programmiersprachen wird auch in Python zwischen Instanzattributen und Klassenattributen (statische Attribute) unterschieden, wobei letztere eher in Ausnahmefällen verwendet werden. Wie der Name schon sagt gehören **Instanzattribute** zu den individuellen Instanzen einer Klasse. D.h. werden zwei Objekte derselben Klasse instanziert, dann wird für beide beim Erzeugen ein jeweils eigener Pool von Instanzattributen angelegt. Der Wert des Instanzattributs ``att`` in Objekt 1 ist völlig unabhängig vom Wert des Instanzattributs ``att`` in Objekt 2 derselben Klasse. Hingegen wird ein **Klassenattribut** pro Klasse nur einmal angelegt. D.h. alle Objekte einer Klasse können gemeinsam auf das eine Klassenattribut zugreifen. Häufig werden Klassenattribute verwendet um die aktuell instanzierten Objekte einer Klasse zu zählen. Klassenattribute werden in Python direkt im ``class``-Block unter Zuweisung eines Initialwerts definiert (d.h. nicht innerhalb des Konstruktors oder einer anderen Methode). Instanzattribute hingegen werden im Konstruktor definiert und mit Initialwerten versehen. Sichtbarkeit ------------- Wie oben bereits erwähnt werden innerhalb von Klassen Attribute und Methoden definiert. Die Sichtbarkeit von Methoden und Attributen ausserhalb der Klasse in der sie definiert sind kann vom Programmierer eingestellt werden. Ein wesentliches Prinzip der objektorientierten Programmierung ist es, Attribute nur über Methoden von aussen zugänglich zu machen, d.h. auf Attribute sollte aus Gründen der Sicherheit und Zugriffskontrolle von ausserhalb nicht direkt zugegriffen werden, sondern nur über Methoden, die u.a. die Zulässigkeit des Zugriffs kontrollieren. Python unterstützt folgende 3 Sichtbarkeitsstufen: * ``public`` Attribute und Methoden sind von ausserhalb direkt über eine Referenz auf das Objekt der Klasse zugänglich. Beispiel: ``ref.att`` und ``ref.meth()`` definieren den Zugriff auf ein ``public`` Attribut ``att`` und eine ``public`` Methode ``meth()``, die in einer Klasse definiert sind, von deren Typ das Objekt ist, auf welches ``ref`` zeigt. Default, also ohne spezielle Kennzeichnung, sind in Python alle Attribute und Methoden ``public``. * ``private`` Attribute und Methoden sind von aussen nicht sichtbar. Attribute und Methoden werden als ``private`` deklariert, indem man ihren Namen mit dem Prefix ``__`` (doppelter Unterstrich) versieht. * ``protected`` Attribute und Methoden werden gekennzeichnet, indem man dem Namen einen einfachen Unterstrich ``_`` voranstellt. Technisch ist für diese Attribute und Methoden der Zugriff von aussen genauso möglich wie im Fall ``public``. Wird ein Attribut oder eine Methode als ``protected`` definiert, so soll das lediglich andere Programmierer darauf hinweisen, dass sie keinen direkten Zugriff von aussen implementieren sollen. Es handelt sich hierbei also nur um eine Konvention oder Empfehlung. Konstruktor ------------- Beim Erzeugen eines Objekts wird für dieses ein eigener Satz von Instanzattributen angelegt. Diese Erzeugung und Initialisierung wird in einer speziellen Methoden durchgeführt, dem Konstruktor. Der Konstruktor wird in Python durch die Kopfzeile:: def __init__(self): definiert. Es handelt sich hierbei um eine spezielle private Methode, die von aussen nicht direkt aufgerufen werden kann, sondern nur automatisch beim Erzeugen eines neuen Objekts aufgerufen wird. Wie alle anderen Methoden erhält auch der Konstruktor als ersten Parameter (``self``) eine Referenz auf das zugehörige Objekt. Im Rumpf des Konstruktors müssen alle Instanzattribute der Klasse angelegt werden. Da diese Attribute zum gerade erzeugten Objekt gehören und dieses mit ``self`` referenziert wird, ist den Namen der Instanzattribute jeweils ``self.`` voranzustellen. Der Konstruktor einer Klasse mit einem Instanzattribut ``instatt`` könnte wie folgt aussehen:: def __init__(self): self.instatt="start" print self.instatt Um die Instanzattribute von Objekten beim Erzeugen individuell einzustellen, muss der Konstruktor diese Initialwerte aufnehmen können. Die Parameterliste des Konstruktors muss also entsprechend erweitert werden:: def __init__(self,inittext): self.instatt=inittext print self.instatt Für die Parameterübergabe an Konstruktoren und Methoden gelten die gleichen Freiheiten wie im Fall der Funktionen, d.h. durch Vorbelegung der Parameter mit Standardwerten muss beim Aufruf nicht zwingend für alle Parameter ein Argument übergeben werden. Destruktor ------------- Ist ``objref`` eine Referenz auf ein Objekt, dann kann das Objekt durch:: del(objref) gelöscht werden. Sollen bei einem derartigen Löschen von Objekten bestimmte Aktionen durchgeführt werden, so kann für die entsprechende Klasse ein Destruktor definiert werden. Der Destruktor ist eine spezielle Methode, die automatisch beim Löschen eines Objekts mit ``del()`` aufgerufen wird. Der Destruktor muss mit der Kopfzeile:: def __del__() definiert werden. In den Rumpf des Konstruktors werden die beim Löschen des Objekts durchzuführenden Anweisungen geschrieben. Erzeugen von Objekten ======================== Objekte einer Klasse ``meineKlasse`` können durch den Aufruf:: ref=meineKlasse(arg1,arg2,...) erzeugt werden. Die innerhalb der geschweiften Klammern angeführten Argumente werden dem Konstruktor übergeben. Enthält der Konstruktor als Parameter nur ``self``, dann können die geschweiften Klammern nach dem Klassennamen ganz weggelassen werden. Beispiel: Definition von Klassen/Objekte erzeugen und benutzen ================================================================== Folgende Klasse kapselt die Daten und Funktionen für eine einfache Kontoverwaltung:: class Konto: angelegteKonten=0 def __init__(self,inhaber,autorisiert=["Bankangestellter"],startkap=0): self.__inhaber=inhaber self.__autorisiert=autorisiert self.__kontostand=startkap Konto.angelegteKonten+=1 def __del__(self): Konto.angelegteKonten-=1 def einzahlen(self,betrag): if (type(betrag)==float or type(betrag)==int or type(betrag)==long) and betrag>0: self.__kontostand +=betrag print "Neuer Kontostand: ",self.__kontostand else: print "FEHLER: Falsche Betragsangabe" return self.__kontostand def auszahlen(self,betrag,initiator): if not initiator in self.__autorisiert: print initiator+" ist nicht berechtigt" elif self.__kontostand < betrag: print "Es befinden sich nur noch %10.2f Euro auf dem Konto" % self.__kontostand else: self.__kontostand -=betrag return self.__kontostand def abfrage(self): return self.__kontostand Die Klasse enthält drei Instanzattribute, die im Konstruktor angelegt werden. Ausserdem ist ein Klassenattribut (``angelegteKonten``) definiert mit dem die Anzahl der angelegten Konten gezählt werden soll. Hierfür wird bei jedem Erzeugen eines neuen Objekts der Klasse die Zählvariable inkrementiert und bei jedem Löschen eines Objekts im Destruktor dekrementiert. Sämtliche Attribute sind als ``private`` deklariert, d.h. man kann auf sie nur über die als ``public`` definierten Methoden zugreifen. Der folgende Programmcode zeigt wie Objekte der Klasse ``Konto`` angelegt und benutzt werden können:: import klassenbeispiele1 as classbsp kontoSchwarz=classbsp.Konto("Schwarz",["Schwarz","Bankangestellter","Papa"],10) print kontoSchwarz.einzahlen(1499.00) print kontoSchwarz.auszahlen(1000, "Freundin") print kontoSchwarz.abfrage() print kontoSchwarz.auszahlen(1600, "Papa") print kontoSchwarz.auszahlen(900, "Schwarz") print "Anzahl der Konten :",classbsp.Konto.angelegteKonten kontoWeiss=classbsp.Konto("Weiss") print "Anzahl der Konten :",classbsp.Konto.angelegteKonten print "Kontostand von Schwarz :",kontoSchwarz.abfrage() print "Kontostand von Weiss :",kontoWeiss.abfrage() del(kontoWeiss) print "Anzahl der Konten :",classbsp.Konto.angelegteKonten Das Programm erzeugt folgende Ausgabe:: Neuer Kontostand: 1509.0 1509.0 Freundin ist nicht berechtigt 1509.0 1509.0 Es befinden sich nur noch 1509.00 Euro auf dem Konto 1509.0 609.0 Anzahl der Konten : 1 Anzahl der Konten : 2 Kontostand von Schwarz : 609.0 Kontostand von Weiss : 0 Anzahl der Konten : 1 Vererbung =========== Soll eine Klasse ``meineKlasse`` von den Klassen ``C1, C2``, usw. abgeleitet werden, dann muss die Klassendefinition mit der Kopfzeile:: class meineKlasse(C1,C2,...): beginnen. Alle Attribute und Methoden der Elternklassen ``C1, C2``,... sind dann auch in ``meineKlasse`` verfügbar und können dort ggf. überschrieben werden.