Python-3-Programmierung: Objektorientiertes Programmieren und Klassen
Inhaltsverzeichnis |
[Bearbeiten] Python und OOP
Dieser Abschnitt bietet eine schnelle Übersicht für Interessierte, die mit den Grundbegriffen der Objektorientierten Programmierung (OOP) vertraut sind, und die ersten Erfahrungen mit Python gemacht haben. Einsteiger sollten zuerst mit dem nächsten Kapitel beginnen.
[Bearbeiten] Klassen
Eine Klasse wird in Python wie folgt definiert:
class A: pass
Das ist die einfachste Klasse, die man definieren kann. Was kann man damit machen?
>>> A <class __main__.A at 0x008D0AB0> >>> a = A() >>> a <__main__.A instance at 0x008D9440>
Wir haben also eine Klasse A im Module __main__ definiert, da wir hier im interaktivem Modus arbeiten. Die Variable a ist eine Instanz von A.
>>> a.x = 5 >>> a.x 5 >>> del a.x >>> a.x Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: A instance has no attribute 'x'
Einer Instanz kann man also beliebige Attribute hinzufügen und wieder löschen
>>> A.z = 6 >>> a.z 6 >>> def eineFunktion(instance): ... print "Na sowas!?!" ... >>> A.eineMethode = eineFunktion >>> a.eineMethode() Na sowas !?!
Auch der Klasse kann man Attribute oder Methoden hinzufügen, die dann sofort den Instanzen zur Verfügung stehen. JavaScript-Adepten wird das vertraut vorkommen, sollte man in Python aber vermeiden, falls man sich selbst und andere Leser des Codes nicht vorsätzlich in die Irre führen will. (oder man in die hohe Kunst der Metaprogrammierung einsteigen will, was aber auf das Gleiche hinaus läuft).
Das Beispiel soll nur deutlich machen, wie in Python Klassen und Instanzen verwaltet werden. Gibt man nämlich folgendes ein
>>> A.__dict__
{'__module__': '__main__', 'z': 6,
'eineMethode': <function eineFunktion at 0x008D77F0>,
'__doc__': None}
>>> a.__dict__
{}
sieht man, dass intern Dictionaries verwendet werden. Eine saubere Klassendefinition sähe so aus:
class A(object): def __init__(self, id): self.id = id def __del__(self): print "im Destruktor" def prettyPrint(self): print "Ich bin eine Instanz mit id=",str(self.id)," von ",self.__class__
In den Klammern in der Klassendefinition werden die Oberklassen angegeben, von denen die aktuelle Klasse abgeleitet ist. Das Schlüsselwort object gibt an, dass die Klasse A eine sogenannte new-style-class ist, die es seit Python 2.2 gibt. Die Methode __init__ ist der Konstruktor der Klasse, der aufgerufen wird wenn eine neue Instanz erzeugt wird. Das Gegenstück ist die Methode __del__, die aufgrufen wird, wenn die del-Anweisung auf eine Instanz der Klasse angewandt wird.
Die selbstdefinierte Methode prettyPrint druckt Informationen zur Instanz aus. Auffällig ist der Parameter self bei allen Methoden. Jede Methode einer Klasse muss im ersten Parameter eine Instanz der Klasse aufnehmen können. Das hängt wieder mit den Dictionaries zusammen. Wenn a eine Instanz von A ist, dann wird der Aufruf
a.prettyPrint()
umgesetzt mit
a.__class__.__dict__['prettyPrint'](a)
Im Dictionary der Klasse von a -also A- wird nach einem Schlüssel prettyPrint gesucht. Das Dictionary sollte dann eine Funktion zurückgeben, der als erstes Argument die Instanz selbst übergeben wird.
self ist übrigens kein Schlüsselwort. Man könnte auch this oder instance schreiben oder bei jeder Methode den Namen des ersten Parameter wechseln. Das wird einem aber nur seltsame Blicke einbringen.
>>> a = A(55) >>> a.prettyPrint() Ich bin eine Instanz mit id= 55 von <class '__main__.A'> >>> a.__class__.__dict__['prettyPrint'](a) Ich bin eine Instanz mit id= 55 von <class '__main__.A'> >>> A.prettyPrint(a) Ich bin eine Instanz mit id= 55 von <class '__main__.A'> >>> A.prettyPrint() Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: unbound method prettyPrint() must be called with A instance as first argument (got nothing instead) >>> >>> a.prettyPrint <bound method A.prettyPrint of <__main__.A object at 0x008D2C70>> >>> A.prettyPrint <unbound method A.prettyPrint> >>> A.__dict__['prettyPrint'] <function prettyPrint at 0x008D7770>
Im Dictionary der Klasse A ist prettyPrint bloß eine simple Funktion. Im Kontext von der Klasse A ist sie eine ungebundene Methode, soll heissen, dass sie an keine Instanz gebunden ist. Im Kontext der Instanz a ist sie eine gebundene Methode. Zum Schluss noch den Destruktor:
>>> del a im Destruktor
[Bearbeiten] Attribute
Wie man oben gesehen hat, kann man jederzeit einer Instanz ein neues Attribut oder eine neue Methode verpassen. Man kann nur eine Empfehlung aussprechen, wie man das handhaben sollte. Klassenkonstanten sollten direkt am Anfang deklariert werden, alle Instanzattribute sollten im Konstruktor definiert werden.
class Person(object): TYPE_NORMAL = 0 TYPE_STRANGE = 1 def __init__(self, name, age): self.name = name self.age = age self.company = None >>> Person.TYPE_NORMAL 0 >>> p=Person('Hans', 34) >>> p.TYPE_NORMAL 0 >>> p.name Hans
Man sollte davon absehen in anderen Methoden (oder gar in Methoden anderer Klassen) Attribute hinzuzufügen oder zu entfernen, auch wenn das möglich ist. Attribute, die noch nicht im Konstruktor belegt werden können, sollten trotzdem deklariert und mit None belegt werden. Achtung: Die beiden "Konstanten" sind im Dictionary der Klasse vorhanden, stehen aber auch den Instanzen zur Verfügung und können dort geändert werden. Falls sie in einer Instanz geändert werden, wird eine Kopie im Dictionary der Instanz angelegt.
>>> p.__dict__ {'age': 34, 'company': None, 'name': 'Hans'} >>> p.TYPE_NORMAL = 2000 >>> p.TYPE_NORMAL 2000 >>> p.__dict__ {'age': 34, 'company': None, 'name': 'Hans', 'TYPE_NORMAL': 2000} >>> Person.TYPE_NORMAL =3000 >>> Person.TYPE_NORMAL 3000 >>> p.TYPE_NORMAL 2000
Obiges Beispiel zeigt auch, dass es statische bzw. Klassenvariablen in Python nicht geben kann.
[Bearbeiten] Private Attribute
Private Attribute können definiert werden, indem man den Namen mit zwei Unterstrichen beginnen lässt
class PrivateTest(object): def __init__(self): self.__privateAttr = "I'm private" def getPrivate(self): return self.__privateAttr >>> pt = PrivateTest() >>> pt.getPrivate() Im private >>> pt.__privateAttr Traceback (most recent call last): File "stdin", line 26, in <module> pt.__privateAttr AttributeError: 'Person' object has no attribute '__privateAttr >>> pt.__dict__ {'_PrivateTest__privateAttr': 'Im private'}
Solche privaten Attribute können nur in Methoden der eigenen Klasse benutzt werden. Auch in abgeleiteten Klassen ist der Zugriff nicht erlaubt. Ein Konstrukt für protected Attribute gibt es nicht. Wie man sieht, wird ein privates Attribut mit dem vorangestellten Namen der Klasse im Dictionary gespeichert. Daran erkennt Python in welchem Kontext der Zugriff erlaubt ist. Mit
>>> pt._PrivateTest__privateAttr
Im private
bekommt man trotzdem Zugriff. Ist also nur ein Hack.
[Bearbeiten] Methoden
Wie oben beschrieben sind Methoden einfach Pythonfunktion, die innerhalb einer Klasse definiert sind und als erstes Argument eine Instanz der Klasse übergeben bekommen.
Da in Python die Variablen nicht typisiert sind ist auch kein echtes Überladen von Methoden möglich. Man kann das nur durch Abfrage des Argumenttyps nachbauen.
import types class Processor(object): .... def processObject(self, obj): """ Takes an Object of class XXX, or an unique ID """ if type(obj) == types.IntType: obj = getXXXByID(obj) elif not (type(obj) == types.ClassType and obj.__name__ == 'XXX'): raise "Error in Argument: Only Int or XXX" #process the obj
[Bearbeiten] Statische Methoden
Mit den Funktionen staticmethod und classmethod lassen sich statische Methode zu einer Klasse hinzufügen. Bei der Definition mit classmethod, muss die Methode im ersten Argument die Klasse aufnehmen, bei der Definition mit staticmethod nicht.
class StaticTest(object): def staticFunctionImpl(): return "Im static" staticFunc = staticmethod( staticFunctionImpl ) def classFunctionImpl(cls): return "Im static in class"+cls.__name__ classFunc = classmethod( classFunctionImpl ) >>> st = StaticTest() >>> StaticTest.staticFunc() Im static >>> StaticTest.classFunc() Im static in class StaticTest >>> st.staticFunc() Im static >>> st.classFunc() Im static in class StaticTest >>> StaticTest.staticFunctionImpl() Traceback (most recent call last): File "stdin", line 1, in <module> StaticTest.staticFunctionImpl() TypeError: unbound method staticMethod() must be called with Person instance as first argument (got nothing instead) >>> st.staticFunctionImpl() Traceback (most recent call last): File "stdin", line 1, in <module> st.staticFunctionImpl() TypeError: staticFunctionImpl() takes no arguments (1 given)
Die Funtion staticmethod stellt die ungebundene Funktion staticFunctionImpl unter dem Namen staticFunc im Dictionary zur Verfügung. Die Methode kann man dann im Kontext der Klasse aufrufen, allerdings nicht im Kontext eines Objekts. Der direkte Aufruf von staticFunctionImpl ist nicht möglich. Im Kontext der Klasse ist es immer noch eine ungebundene Methode, und im Kontext eines Objekts hat die Methode die falsche Signatur (nämlich der fehlende erste Parameter für die Instanz). Bei der Funktion classmethod gilt gleiches. Nur muss die Methode hier im, erstes Argument die Klasse entgegen nehmen können.
Ab Python 2.4 stehen sogenannte Funktionsdekoratoren zur Verfügung. Sie vereinfachen die Schreibeweise zu
class StaticTest(object): @staticmethod def staticFunc(): return "Im static" @classmethod def classFunc(cls): return "Im static in class"+cls.__name__
Man kann auch nachträglich ausserhalb der Klassendefinition statische Methoden hinzufügen.
def aFunction(): return 'Im an extern function' >>> StaticTest.anotherStaticFunc = staticmethod( aFunction ) >>> StaticTest.anotherStaticFunc() Im an extern function
[Bearbeiten] Abstrakte Methoden
In Python gibt es keine Schlüsselwörter für Schnittstellen oder abstrakte Klassen. Eine Möglichkeit das nachzubilden besteht darin, in der Oberklasse die Methode zu definieren, aber darin eine Exception zu werfen.
class AbstractEventHandler(object): def processEvent(self, event): raise NotImplementedError
Allerdings wird das Problem dann erst zur Laufzeit entdeckt.
[Bearbeiten] Vererbung
Man kann eine Klasse von einer anderen ableiten, indem man den Namen der gewünschten Oberklasse in Klammern bei der Defintion angibt. Alle Methoden und Attribute (außer den privaten) stehen dann der abgeleiteten Klasse zu Verfügung.
class A(object): def pp(self): return "Im of class A" def say(self, text): return "Im class A and say: "+text class B(A): def pp(self): return "Im class B" >>> a = A() >>> b = B() >>> a.pp() Im of class A >>> a.say('hallo') Im class A and say: hallo >>> b.pp() Im class B >>> b.say('Hi') Im class A and say: hi
Hat man eine Methode der Elternklasse überschrieben und möchte trotzdem auf die Methode der Elternklasse zugreifen steht dafür die Funktion super zur Verfügung, die die Dictionaries der Elternklassen nach der Methode durchsucht. Die Funktion super steht nur für New-Style-Klassen zur Verfügnung.
class B(A): def say(self, text1, text2): return 'My Papa says: '+super(B, self).say(text1) +'\n' \ +'and I say: '+text2 >>> b=B() >>> b.say('Hallo', 'Hi') My Papa says: Im class A and say: Hallo and I say: Hi
Python ermöglicht Mehrfachvererbung. Der abgeleiteten Klasse stehen dann alle Attribute und Methoden der Elternklassen zur Verfügung. Gibt es Methoden mit gleichem Namen in der Elternklasse, gewinnen die Methoden deren Klasse zuerst aufgeführt wird:
class A(object): def pp(): return "von A" class B(object): def pp(): return "von B" class C(A,B): pass >>> c=C() >>> c.pp() von A class C(B,A): pass >>> c=C() >>> c.pp() von B
[Bearbeiten] Fazit
Man merkt Python an, dass es ursprünglich nicht als objektorientierte Sprache entworfen wurde, sondern dass nach und nach objektorientierte Konzepte aufgenommen wurden.
Wer eine Skriptsprache benötigt, die OOP weitergehend unterstützt, sollte einen Blick auf Ruby werfen. Wer sich mit der reinen Lehre der Objektorientierung beschäftigen will, sollte sich Smalltalk anschauen, oder bestimmte Java-Bibliotheken durchforsten. (In der Swing-Bibliothek wird so gut wie jedes bekannte Design-Pattern verwendet.)
Trotzdem, Python rocks!!
[Bearbeiten] Objekt-Orientiertes Programmieren und Klassen
[Bearbeiten] Übersicht
Python eignet sich sehr gut, um in die objektorientierte Programmierung (OOP) einzusteigen.
Was bedeutet objektorientierte Programmierung?
Bei der nicht objektorientierten Programmierung wird zwischen Daten und Funktionen unterschieden: Daten sind Werte (Ausprägungen) eines bestimmten Datentyps; Funktionen arbeiten mit Werten eines bestimmten Datentyps, an den sie angepasst sind. Für jeden Datentyp müssen entsprechend eigene Funktionen erstellt werden. Wenn sich ein Datentyp ändert, müssen ggf. alle Funktionen, die mit ihm arbeiten, ebenfalls angepasst werden.
In der OOP werden Datentypen mit zugehörigen Funktionen zu Klassen zusammengefasst. So wie Daten die Ausprägungen eines bestimmten Datentyps sind, so sind Objekte die Ausprägungen einer bestimmten Klasse - in der OOP Terminologie werden Objekte als Instanzen einer Klasse bezeichnet. Ein Objekt enthält also Werte (Attribute) bestimmter Datentypen und Funktionen (Methoden), die mit diesen Datentypen arbeiten können.
Der größte Vorteil dieser Vorgehensweise ist die Abstraktion. Weil ein Objekt die in ihm gespeicherten Daten kennt und durch die Funktionen ebenfalls weiß, wie diese Daten zu bearbeiten sind, muss (und sollte) beides nicht an anderen Stellen im Programm bekannt sein. Dort wird von diesen Details abstrahiert - es genügt das Objekt zu kennen und zu wissen, wie es verwendet wird. Das Verbergen der Implementierungsdetails heißt in der OOP Kapselung.
Objekte besitzen (meistens) öffentliche Methoden (Schnittstellen), über die mit ihnen kommuniziert werden kann. Ändern sich die im Objekt gekapselten Datentypen und/oder die Funktionen, hat das keine Auswirkungen auf andere Programmteile, solange sich die Schnittstellen nicht verändern. Weil die Auswirkungen sozusagen lokal bleiben, wird diese Eigenschaft als Lokalität bezeichnet. Dieser Vorteil von OOP ist nicht per se gegeben, sondern muss durch konsequentes Softwaredesign erkauft werden.
Zusätzlich spielen in der OOP Konzepte wie Vererbung und Polymorphie wichtige Rollen. Beide dienen dazu, die Behandlung von unterschiedlichen Objekten zu verallgemeinern. Erste setzt dazu in der Vererbungshierarchie von Klassen an – gleichartige Attribute und Methoden verschiedener Klassen werden zu Superklassen zusammengefasst. Von diesen Superklassen können mehrere spezialisierte Klassen diese Attribute und Methoden erben, ohne sie selbst mehrfach implementieren zu müssen. Polymorphie setzt beim Objekt-/Datentyp von Nachrichtenempfängern und Parametern an – erst zur Laufzeit wird entschieden, welche Klasse in einer Vererbungshierarchie oder welche Methode eines Objekts in einem Ausdruck gemeint ist.
[Bearbeiten] Objekte
Ein Objekt ist eine bestimmte "Sache", die ein laufendes Programm als eine Einheit bearbeiten kann. Zum besseren Verständnis nimmt man idealerweise an, dass ein Objekt das virtuelle Abbild eines realen Gegenstandes aus der Wirklichkeit ist. Dabei werden für jedes einzelne Objekt nur die Informationen erfasst, welche für die Datenverarbeitung als notwendig erachtet werden. Jedes Objekt benötigt einen eindeutigen Bezeichner (Namen). Diesem Objekt werden bei der Erstellung (Initiierung) Eigenschaften (Attribute) und Fähigkeiten (Methoden) zugewiesen. Im Laufe des Programmes können diese allerdings wieder über Methoden verändert werden.
[Bearbeiten] Klassen
Klassen dienen als Vorlagen für Objekte. Sie definieren Attribute in Form von Variablen und Methoden in Form von Funktionen. Doch erst beim Erzeugen von Objekten bekommen Attribute konkrete Werte zugewiesen. Über Methoden können diese Werte manipuliert werden.
Das Verständnis um den Unterschied zwischen Klassen und Objekten ist für das OOP essenziell: Klassen sind eine Art Schablone, aus denen Objekte erzeugt werden können. Ein Objekt ist dagegen eine konkrete Ausprägung - eine Instanz - einer Klasse. Objekte existieren erst, wenn sie mit Hilfe der Schablone erzeugt werden - sie besitzen eine Identität - ihre Attribute nehmen konkrete Werte ein. Klassen hingegen existieren sofort, nachdem sie niedergeschrieben wurden - sie definieren lediglich die Attribute und Methoden. Das ist eine stark vereinfachte Darstellung der Tatsachen, sie ist aber an dieser Stelle zweckmäßig für das Verständnis.
Folgender Codeschnipsel definiert eine Klasse "Linie". Dies geschieht mit dem Schlüsselwort class gefolgt vom gewünschten Namen, den die Klasse erhalten soll. Dahinter können, in Klammern gesetzt, ein oder mehrere Superklassen folgen. Durch die Angabe der Superklassen werden alle Attribute und Methoden dieser an die Klasse "Linie" vererbt. Objekte der Klasse "Linie" können später genauso behandelt werden, als seien sie Objekte der Superklassen. Die Klasse "Linie" wird als spezialisiert bezeichnet, weil sie "noch mehr kann" - also spezialisierter ist - als ihre Superklassen.
class Linie(object): def __init__(self, x1, y1, x2, y2, color="black"): self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 self.color = color self.id = None def draw_at(self, canvas, x, y): self.id = canvas.create_line(self.x1+x, self.y1+y, self.x2+x, self.y2+y, fill = self.color)
Ich (Dookie) empfehle, alle eigenen Klassen, die nicht von schon bestehenden Klassen oder eingebauten Typen (int, str, list,...) abgeleitet sind, wie im Beispiel gezeigt, von der eingebauten Klasse object abzuleiten. Das macht sie zu so genannten "Newstyle-Klassen", welche einige Besonderheiten bereitstellen; mehr dazu weiter unten.
Seit Python 2.2 erzeugen alle eingebauten Datentypen Objekte - Datentypen selbst sind also Klassen. Alle Datentypen können auch als Superklassen für eigene Klassen dienen - diese sind damit immer Newstyle-Klassen.
[Bearbeiten] Attribute
Der Zugriff auf die Variablen im obigen Beispiel unterscheidet sich etwas von der bisherigen Art und Weise, auf Variablen zuzugreifen. Die Variablen "self", "color", "x1", "x2", "y1" und "y2" sind normale Variablen. Sie werden beim Methodenaufruf übergeben, wobei self eine Referenz auf ein Objekt, genauer gesagt eine Referenz auf eine Instanz genau dieser Klasse, ist.
Die Variablen "self.color", "self.x1", "self.x2", "self.y1" und "self.y2" sind so genannte Instanzvariablen. Es ist eine alternative Bezeichnung für die Attribute eines Objektes. Sie drückt aus, dass diese Variablen nur existieren, wenn ein zugehöriges Objekt existiert. Ein zugehöriges Objekt muss aber existieren, weil eine Referenz auf dieses Objekt übergeben werden konnte - "self".
[Bearbeiten] Methoden
Funktionen, die innerhalb von Klassen definiert wurden, werden Methoden genannt. Über Methoden können Attributwerte von Objekten verändert werden. Alternativ ist die Sichtweise "Objekte empfangen Nachrichten über Methoden" vorteilhafter. Einem Objekt wird damit selbst überlassen, bei einem Methodenaufruf - also einer Nachricht - einen Attributwert zu ändern oder etwas anderes zu tun. Das ist der oben erwähnte Preis für Lokalität.
Die Methoden im obigen Beispiel: __init__(...) und draw_at(...) werden Instanzmethoden genannt. Sie bekommen als ersten Parameter self bei ihrem Aufruf übergeben. Über "self" können diese Methode auf eine bestimmte Instanz der Klasse - also auf ein konkretes Objekt - und die dort abgelegten Werte zugreifen. Spezielle Instanzmethoden die mit __ beginnen und enden bieten eine fest vorgegebene Funktionalität. So wird z.B. die Methode __init___ (der Konstruktor) immer beim Erzeugen eines Objekts automatisch aufgerufen. In ihm können z.B. Attribute mit Werten belegt werden, wie im Beispiel geschehen.
Daneben gibt es auch noch Klassenmethoden. Diese werden durch die Funktion classmethod(Methodenname) in der Klassendefinition erzeugt. Sie erhalten beim Aufruf keine Instanz (self) als ersten Parameter, sondern eine Referenz auf die Klasse. [Wozu diese Methoden ?]
Als letztes gibt es noch statische Methoden, die mit der Funktion staticmethod(Methodenname) erzeugt werden. Sie haben keinen speziellen ersten Parameter und können auch nicht auf die Attribute eines konkreten Objektes zugreifen. [Ihre Vorteile müssen hier noch dokumentiert werden.]
[Bearbeiten] Vokabular
[Bearbeiten] Übungsaufgaben
<< Inhaltsverzeichnis