Objektorientierte Programmierung
Einführung
Python is a dynamic object-oriented programming language that can be used for many kinds of software development. ( www.python.org)
Motivation
Werkzeug für den Programmierer
Python nennt sich eine Multiparadigmen Programmiersprache. Das heißt, Python stellt es dem Programmierer frei, die Lösung seines Problems auf verschiedene Arten zu formulieren.
Der Programmierer kann funktionale, objekt-orientierte, strukturierte, aspektorientierte Techniken zum Codieren verwenden. Als mächtige Erweiterung der objekt-orientierten Programmierung unterstützt Python metaclass programming.
Trotz dieser verschiedene Stilrichtungen versteht sich Python insbesondere als objekt-orientierte Programmiersprache.
Strukturmittel für den Designer
Die objektorientierte Programmierung bietet sehr mächtige Komponenten an um Software zu strukturieren. Examplarisch können die graphischen Bibliotheken Swing, QT oder die Frameworks Eclipse und ACE genannt werden. Der Siegeszug der objektorientieren Programmierung ist auch ein Siegeszug der Design Patterns.
Begrifflichkeit für den Systemadministrator
Die Begrifflichkeit und Funkionalität der objektorientierten Sichtweise wird für den Systemadministrator immer wichtiger.
So setzen das Lightweight Directory Access Protocol LDAP und die Windows Power Shell auf OO-Konzepten auf.
Auf http://de.wikipedia.org/wiki/Windows_PowerShellwird die Windows Power Shell folgendermassen charaktisiert.
Die auf dem .NET-Framework in der Version 2.0 basierende Windows PowerShell verbindet die aus Unix-Shells
bekannte Philosophie von Pipes und Filtern mit dem Paradigma der objektorientierten Programmierung.
Begrifflichkeit
Objekt versus Klasse
- ein Objekt ist eine Instanz einer Klasse, eine konkrete Ausprägung einer Klasse
- so ist der Zahl 5 Instanz der Klasse Integer, die Liste [1,2,3,4] Instanz der Klasse Liste
- alle Instanzen, alle Objekte vom Typ Liste zeichnen sich durch das gleiche Interface aus:
>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__',
'__doc__', '__eq__', '__ge__','__getattribute__', '__getitem__', '__getslice__', '__gt__',
'__hash__', '__iadd__', '__imul__', '__init__', '__iter__','__le__', '__len__', '__lt__',
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__',
'__setattr__', '__setitem__', '__setslice__', '__str__', 'append', 'count', 'extend',
'index', 'insert', 'pop', 'remove','reverse', 'sort']
- Klasse
- Eine Klasse beschreibt die Methoden und Variablen einer Menge gleichartiger Objekte.
- Objekt
- Ein Objekt ist eine konkrete Ausprägung einer Klasse zur Laufzeit eines Programmes.
Konzepte
- Objektorientiertes Programmieren ist kein definierter Begrifflichkeit, dennoch lassen sich drei Paradigmen herausarbeiten, die für objektorientiertes Programmieren charakteristisch sind:
- Kapselung, Vererbung und Polymorphie
- jede Programmiersprache, die sich objektorientiert nennt, wird diese Paradigmen individuell umsetzen
Kapselung
- Kapselung
- Ist die Bindung einer Datenstruktur mit den Methoden, die auf dieser Datenstruktur wirken.
- die Datenstruktur (Klasse) stellt Methoden zur Verfügung, über die die Aussenwelt mit ihr kommunizieren kann
- dies Preisgeben der Interna nach Aussen wird als Interface der Klasse bezeichnet, während das Interne der Klasse die Implementierung darstellt
- die Klasse kapselt sich von der Aussenwelt ab
class HumanBeing(object):
def __init__(self,na):
self.__name= na
def getName(self):
return self.__name
def changeName(self,newName):
self.__name= newName
huber= HumanBeing("Huber")
maier= HumanBeing("Maier")
# Huber heiratet Maier
huber.changeName("Huber-"+maier.getName() )
- mittels der öffentlichen Methoden
getName()
and changeName()
kann mit Objekten vom Typ HumanBeing kommunizieren werden, während die Methode __init__
und die Variable self.__name
nicht zugänglich ist
- betont man bei der Kapselung das Verbergen von Attributen vor der Aussenwelt, so spricht man gerne von Information Hiding
Vererbung
- Vererbung
- Bezeichnet die Spezialisierung einer Datenstruktur, wobei die neue Datenstruktur alle Eigenschaften der ursprünglichen Datenstruktur (Klasse) erbt.
- die neue Unterklasse ist eine is-a Spezialisierung der Oberklasse
- ein Mann ist ein menschliches Wesen, dies sollte sich doch in meiner Humen Being Klassenstruktur widerspiegeln
class Man(HumanBeing):
def getSex(self):
return "male"
class Woman(HumanBeing):
def getSex(self):
return "female"
schmitt=Woman("Schmidt")
schmitt.changeName("Schmitt")
- die Klasse Woman kann auf die von HumanBeing geerbten Methoden zugreifen
- die Eigenschaft, ein Teil der Funktionalität durch Vererbung geschenkt zu bekommen, wird oft als das Vorteil von OO beschrieben: code reuse
- das Klassendiagramm der Unified Modeling Language (UML) hat sich als die standardisierte Weise etabliert, Klassenstrukuren zu visualisieren
Polymorphie
- Polymorphie
- Bezeichnet die Eigenschaft einer Methode sich abhängig von der Datenstruktur verschieden verhalten zu können.
- eigentlich will ich kein gleichgeschlechtliche Ehe erlauben
def couldMarry( firstHuman, secondHuman):
return firstHuman.getSex() != secondHuman.getSex()
if ( couldMarry( huber , maier ) ):
huber.changeName("Huber-"+maier.getName() )
- zur Laufzeit werden die Methoden
getSex()
auf die richtige Klasse abgebildet, ändern ihre Gestalt
Sei kreativ: Erzeuge ein paar Männer und Frauen, kommuniziere mit ihnen.
Details
Objektmodell
Klassen
- eine Klasse ist ein abstrakter Obergriff für die Beschreibung der gemeinsamen Struktur und des gemeinsamen Verhaltens von Objekten
- die Attribute umfassen Variablen und Methoden
- durch das Schlüsselwort class wird eine Klasse definiert
- die Klasse Human Being besitzt folgende Attribute
- HumanBeing.__init__
- HumanBeing.getName
- HumanBeing.changeName
- Klasse versus Instanz
- eine Klasse definiert das Aussehen einer Instanz
- self
- da in der Regel mehrere Instanzen einer Klasse instanziiert werden, ist es natürlich notwendig, die Instanzen (Objekte) auseinander zu halten
- daher werden alle Attribute eines Objektes mit dem Argument self gebunden
- die Aufrufsyntax in der Klassendefinition (Klassenname HumanBeing) ist demzufolge
- für Variablen: self.name
- für Methoden: self.changeName("newName") oder HumanBeing.changeName(self,"newName")
- __init__
- die Methode __init__() nimmt eine Sonderstellung ein, denn sie wird, insofern sie definiert ist, bei jedem Instanziieren eines Objekts prozessiert
- Klassen- versus Instanzvariable
- Variablen, den man in der Klassendefinition nicht über das Wort self qualifiziert, werden an die Klasse gebunden und daher auch Klassenvariablen genannt
- um sie aus den Methoden heraus ansprechen zu können, muß explizit der Klassenname vorangestellt werden
- gerne werden Klassenvariablen dazu verwendet, die Anzahl der Instanzen zu zählen
class Student(object):
number=0
def __init__(self,n):
Student.number += 1
self.__name= n
# Student.setID(self,str(n+Student.number))
self.setID(n+str(Student.number))
def getNumber(self):
return Student.number
def setID(self,id):
self.__ID= id
def getID(self):
return self.__ID
nun will ich die Klasse nutzen:
>>> schmitt=Student("Schmitt")
>>> schmitt.getNumber()
1
>>> huber=Student("Huber")
>>> maier=Student("Maier")
>>> grimm=Student("Grimm")
>>> schmitt.getNumber()
4
>>> huber.getID()
'Huber2'
>>> del grimm
>>> schmitt.getNumber()
4
Leider habe ich vergessen, die Anzahl der Studenten beim Ableben eines Studenten del grimm zu dekrementieren. Implementiere und teste eine Methode __del__mit der geschilderten Funktionalität, die vom Python Interpreter beim Löschen eines Objekts automatisch aufgerufen wird.
Objekte
- ein Objekt tritt durch das Instanziieren einer Klasse ins Leben
- durch den Aufruf
>>> schmitt= Student("Schmitt")
wird das Objekt schmidt erzeugt und die __init__Methode der Klasse Student prozessiert
- wir können nun mit dem Objekt interagieren
>>> schmitt.getID()
'Schmitt1'
Konzepte vertieft
Kapselung vertieft
- Python besitzt eine freizügige Zugriffskontrolle, denn es existiert nur die Konvention, dass Methoden oder Variablen, die mit __ beginnen, privat sind
- da Python intern keinen Unterschied zwischen Methoden und Variablen einer Klasse macht, spricht man von Attributen einer Klasse
- für den Zugriff auf die Interna einer Klasse haben sich die Schlüsselwörter private, protected und publicsprachenübergreifend etabliert:
- private : auf die Attribute kann nur aus der Klasse selbst heraus zugegriffen
- protected : auf die Attribute kann darüber hinaus von den abgeleiteten Klassen zugegriffen werden
- public : auf die Attribute existiert keine Zugriffskontrolle
Definiere eine Klasse mit zwei Attributen.
- Ein Attribut soll mit __ beginnen und das andere Attribut mit __ beginnen und enden.
- Instanziere ein Objekt dieser Klasse und rufe die Attribute darauf auf.
Nur mit __ beginnende Attribute einer Klasse werden vermangelt . Diese Namensmodifikation unterstreicht die Privatheit der Attribute. Attribute, die mit __beginnen und enden stellen die Implementierung der Klasse dar und sind für die Python Runtime reserviert.
Vererbung vertieft
- ist das Erzeugen einer neuen Klasse, die eine bestehende Klasse spezialisiert oder modifiziert
- bei der Vererbung differieren die Programmiersprachen
- Mehrfachvererbung: Python und C++ unterstützen im Gegensatz zu Java Mehrfachvererbung
- abstrakte Klassen: Python 2.* kennt keine abstrakten Basisklassen
- abstrakte Basisklassen oder auch Interface sind Klassen, die nicht instanziert werden, sondern lediglich das Verhalten der von ihr abgeleiteten Klassen festlegen
- die abgeleiteten Klassen müssen das Interface implementieren um instanziiert werden zu können
- die Ursprungsklasse wird Basisklasse oder Superklasse, die neue Klasse abgeleitete Klasse oder Unterklasse genannt
- die abgeleitete Klasse erbt die Attribute der Basisklasse, definiert sie gegebenfalls neu und führt neue Attribute ein
- Beispiel:
class DesignStudent(Student):
def __init__(self,n):
Student.__init__(self,n)
def getID(self):
return "Designer: " + Student.getID(self)
- der Aufruf des DesignStudenten führt zu folgender Ausgabe
>>> des= DesignStudent("Frank")
>>> des.getID()
'Designer: Frank1'
>>> des.getNumber()
1
- dem DesignStudent stehen die gleichen Methoden wie dem Student zur Verfügung
>>> dir( DesignStudent )
['__doc__', '__init__', '__module__', 'getID', 'getNumber', 'number', 'setID']
- sowohl im Initialisierer __init__, als auch in der Methode getID nutze ich explizit die Funktionalität der Basisklasse um
- die Basisklasse richtig mit den Namen des Studenten zu initialisieren und den Zähler zu inkrementieren
- die ID Darstellung der Basisklasse ausgeben zu lassen und sie zu verschönern
Erweitere die Klassenstruktur um einen weiteren Studenttyp, damit dieser seine Methode getID überschreiben kann.
- Aufruf von Basisklassenmethoden:
- Während bei old style Klassen die aufzurufende Klasse explizit angegeben werden muß, findet bei new_style Klassen dies mapping durch die Angabe des Schlüsselworts super automatisch statt.
class OldStyle:
... def __init__(self):
... print "OldStyle"
...
>>> class OldStyle2(OldStyle):
... def __init__(self):
... OldStyle.__init__(self)
... print "OldStyle2"
...
>>> OldStyle2()
OldStyle
OldStyle2
>>> class NewStyle(object):
... def __init__(self):
... print "NewStyle"
...
>>> class NewStyle2(NewStyle):
... def __init__(self):
... super(NewStyle2,self).__init__()
... print "New2Style"
...
>>> NewStyle2()
NewStyle
NewStyle2
-
- Python 3.* unterstützt nur noch new style Klassen
Polymorphie vertieft
Die Eigenschaft Pythons, zur Laufzeit zu entscheiden, von welchem Datentyp das konkrete Objekt ist, wird als dynamische Typisierung bezeichnet und bildet die Grundlage für die Polymorphie.
Im Gegensatz zum dynamischen Typisieren von Python ist das statische Typisieren von Java oder C++ deutlich aufwändiger.
Diese Sprachen, die zur Compilezeit typisieren wollen, müssen ihre Entscheidung bis zur Laufzeit aufschieben, um Polymorphie unterstützen zu können.
Gegeben sei folgendes Klassendiagramm.
Implementiere es so, daß jede Instanz einer Klasse ihren Klassennamen ausgibt. Verwende die Klasse anschließend in folgender Schleife.
import random
>>> for i in range(5):
... random.choice((Ball(),Handball(),Basketball())).getName()
...
Basketball
Handball
Ball
Basketball
Ball
Erst durch die Auswahl von random.choice() wird zu Laufzeit bestimmt, von welchem Typ das zu instanziierende Objekt ist, auf dem dann die Methode getName() aufgerufen wird.
Operator Überladung
- Operator Überladung
- Ist die Definition von Operatoren auf Datentypen.
- entsprechend der __init__ Methode, die bei der Objektinstanzierung prozessiert wird, gibt es für arithmetische Operationen Methoden, die automatisch prozessiert werden
- Was nötig ist, um sich Integer zu nennen, zeigt der folgende Aufruf:
>>> dir( 5 )
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '
__divmod__', '__doc__', '__float__', '__floordiv__', '__getattribute__', '__getnewargs__', '__hash__',
'__hex__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__',
'__new__', '__nonzero__','__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__',
'__rdivmod__', '__reduce__','__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__',
'__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__',
'__setattr__', '__str__', '__sub__', '__truediv__', '__xor__']
- diese hooks müssen idealerweise in einem eigenen Datentyp definieren, damit dieser sich wie ein natürliche Zahl verhält
Aperitif
Mein Datentyp MyNumber unterstützt die Addition und Multiplikation von MyNumber-Typen und built-in Datentypen mit einer kleinen Einschränkung (Bindung der Methode an das Objekt).
Ich biete die Funktionalität sowohl über Operator Überladung und über explizite Methodenaufufe an, um den Unterschied zu verdeutlichen.
- implizit: __add__ und __mul__ werden durch + und * von Python als hook Methoden aufgerufen
- explizt: add und mul
Die weiteren Methoden dienen nur der Ausgabe und der Implementierung der arithmetischen Operationen. Obwohl die Semantik der Operationen identisch ist, unterscheidet sich doch der Aufruf deutlich.
class MyNumber(object):
"rigth operand must be instance of type MyNumber or built-in number"
def __init__(self,num):
"set the value for MyNumber"
self.number= num
def __implAddition( self, Num ):
"implementation of addition"
if ( isinstance( Num, MyNumber )) :
return MyNumber( self.number + Num.number )
else:
return MyNumber( self.number + Num )
def __implMultiplication( self, Num ):
"implementation of multiplication"
if ( isinstance( Num, MyNumber )) :
return MyNumber( self.number * Num.number )
else:
return MyNumber( self.number * Num )
def __add__(self,Num):
"overloading additon"
return self.__implAddition(Num)
def __mul__(self,Num):
"overloading multplication"
return self.__implMultiplication( Num )
def add(self,Num ):
"support addition with explicite method"
return self.__implAddition( Num )
def mul(self,Num ):
"support multiplication with explicite method"
return self.__implMultiplication( Num )
def __repr__(self):
"support output of number"
return str(self.number)
Ein bißchen Arithmetik:
>>> one=MyNumber.MyNumber(1)
>>> four=MyNumber.MyNumber(4)
>>> five=MyNumber.MyNumber(5)
>>> sixty= MyNumber.MyNumber( 60 )
>>> one + four
5
>>> one.add(four )
5
>>> one + 3.4
4.4
>>> one.add(3.4)
4.4
>>> five * 3.5
17.5
>>> five.mul(3.5)
17.5
>>> sixty * ( (five * 60) + 31 ) + four
19864
>>> sixty.mul( five.mul(60).add(31) ).add(four)
19864
Sowohl __add__ als auch add verwenden die Methode __implAddition. Durch das Überladen der hook Methode __add__ erreiche ich, daß beim Aufruf des + Operators der Klasse MyNumber diese Methode verwendet wird.
Kommt dann noch ein Namensraum hinzu, kann Artithmetik leicht unübersichtlich werden.
Trennung von Interface und Implementierung
Die Magie von Operator Überladung besteht darin, daß der Python Interpreter Methodenaufrufe implizit auf hook Methoden abbildet. Diese hook Methoden besitzen die zwei Unterstriche am Anfang und Ende des Methodennamens.
Diese Trennung der öffentlich verwendeten Methoden von der intern definierten Funktionalität ist die Trennung von Interface und Implementierung. So ist die Addition + das Interface, das durch die Methode __add__ angeboten wird.
Durch die Implementierung definierter hook Methoden in einer Klasse, unterstützt diese ein definiertes Interface. Gerne spricht man daher davon, daß die Klasse ein Interface umsetzt bzw. ein Protokoll implementiert. Eine Klasse, die Methoden __iter__ und next definiert, implementiert das Iterator Protokoll.
Die Trennung von Interface und Implementierung ist ein wichtiger ( der wichtigste ) Aspekt des OO-Entwurfs.
Mächtigkeit
Operator Überladung ist die Python Art das Verhalten eigener Datentypen zu definieren.
Gleichzeitig zeigen diese Mechanismen die Funktionsweise des Python Interpreters auf und dienen dem tieferen Verständnis von Python.
Es folgen die beliebtesten und wichtigstenhook Methoden.
Indexzugriff
- __getitem__ und __setitem__
>>> class Indexer(object):
... def __init__(self):
... self.__values={}
... def __getitem__(self,index):
... return self.__values.get(index,2**index)
... def __setitem__(self,index,value):
... self.__values[index]=value
...
>>> ind=Indexer()
>>> ind[3]="THREE"
>>> ind[7]="SEVEN"
>>> ind[2]
4
>>> ind[3]
'THREE'
>>> for i in range(10):print ind[i],
...
1 2 4 THREE 16 32 64 SEVEN 256 512
Durch die zwei Methoden __getitem__ und __setitem__ unterstützt meine Klasse Indexerden lesenden und schreibenden Indexzugriff. Die Instanzvariable self.__values ermöglicht es mir einen Zustand aufzubauen. Objekte, die das Index Protokoll implementieren, sind implizit iterierbar.
Iterator Protokoll
>>> class Fak(object):
... def __init__(self):
... self.__fak=1
... self.__count=1
... def __iter__(self): return self
... def next(self):
... self.__fak *= self.__count
... self.__count += 1
... return self.__fak
...
>>> fak=Fak()
>>> for i in range(20): print fak.next(),
...
1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600 6227020800
87178291200 1307674368000 20922789888000 355687428096000 6402373705728000 121645100408832000 2432902008176640000
>>> fak.next()
51090942171709440000L
>>> fak.next()
1124000727777607680000L
>>>
- __iter__ gibt ein Objekt zurück, das das Iteratorprotokoll unterstützt
- next: gibt das nächste Item zurück
Falls die Methoden __iter__ und next nicht implementiert sind, wird in einem Iteratorkontext die hook Methode __getitem__ von Python verwendet.
Python unterstützt Generator Funktionen. Dies sind Funktionen mit der Anweisung yield . Beim Aufruf einer Generator Funktion erzeugt die Python Laufzeitumgebung implizit einen Iterator mit einer Funktion next().
Attributzugriff
- __getattr__ und __setattr__
>>> class CaseInsensitive(object):
... def __getattr__(self,attr):
... if ( not attr.upper() in self.__dict__):
... raise KeyError(attr)
... return self.__dict__[attr.upper()]
... def __setattr__(self,attr,val):
... self.__dict__[attr.upper()]=val
...
>>> c=CaseInsensitive()
>>> c.test
Traceback(most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getattr__
KeyError: 'test'
>>> c.test=100
>>> c.test
100
>>> c.TeSt
100
>>> c.TEST=1000
>>> c.test
1000
Die Klasse CaseInsensitive kapselt die Zugriffe auf ihre Attribute.
- lesender Zugriff
- in der hook Methode __getattr__ wird in dem Objektdictionary self.__dict__ nach dem Attribut in Grossbuchstaben gesucht
- existiert das Attribut nicht, wirft die Methode eine Exception mit dem unmodifizierten Attribut
- schreibender Zugriff
- in der __setattr__ Methode wird das Attribut in Grossbuchstaben in dem Objektdictionary self.__dict__ gesetzt
Ausgabe von Daten
>>> class Output(object):
... def __init__(self,num): self.__num= num
... def __repr__(self): return "__repr__ :" + repr(self.__num)
... def __str__(self): return "__str__: " + str(self.__num)
...
>>> a= Output( 1.0/3.1 )
>>> a
__repr__ :0.32258064516129031
>>> repr(a)
'__repr__ :0.32258064516129031'
>>> print a
__str__: 0.322580645161
Beide hook Methoden ermöglichen es, Objekte vom Typ Output auszugegeben.
Während __repr__ das Datum in möglichst genauer Form ausgibt, liegt der Fokus von __str__ in einer möglichst lesbaren Form.
Es gibt noch ein paar Besonderheiten:
- im Python Interpreter wird jede Ausgabe mittels repr, das implizit __repr__ verwendet, interpretiert
- bei der Ausgabe ist __repr__ ein Fallback für __str__ ; dies gilt aber nicht umgekehrt
- folgende Beziehung muß gelten: eval(repr(a))== a
>>> eval( repr( 1.0/3.1) ) == 1.0/3.1
True
Funktoren
Functoren sind Objekte, die wie Funktionen aufgerufen werden können. Sie besitzen einerseits die einfache Aufrufsemantik einer Funktion und andererseits den Zustand eines Objekt.
Der Begriff Funktor ist ein bißchen C++ lastig. Ein Python Objekt, das den Operator __call__ implementiert, wird gerne als callable object bezeichnet.
class Accumulator(object):
def __init__(self, n):
self.__acc = n
def __call__(self, n):
self.__acc += n
return self.__acc
Instanzen vom Typ Accumulator verhalten sich wie Funktionen und besitzen, da sie Objekte sind, einen Zustand.
>>> start5= Accumulator(5)
>>> start5(10)
15
>>> start5(20)
35
>>> startDies= Accumulator("Dies")
>>> startDies(" ist")
'Dies ist'
>>> startDies(" ein")
'Dies ist ein'
>>> startDies(" Test")
'Dies ist ein Test'
Die built-in Funktion callable evaluiert, ob ein Objekt aufrufbar ist.
>>> callable( Accumulator("" ))
True
>>> class Test(object): pass
...
>>> callable(Test() )
False
callable( lambda x: x*x)
True
>>>
Vergleich
Um Klassen mit einer spezifischen Vergleichssemantik zu implementieren, existieren in Python zwei explizite, verschiedene Konzepte, rich comparison und comparison. Während die erste Variante erlaubt, die Vergleichssemantik für jeden einzelnen Operator zu definieren, definiert die zwei Variante einen Vergleichsoperator für alle Vergleichsoperationen.
- rich comparison
Operator | hook Methode |
< |
__lt__ |
> |
__gt__ |
<= |
__le__ |
>= |
__ge__ |
== |
__eq__ |
!= <> |
__ne__ |
- nur die Operatoren können verwendet werden, die explizit definiert werden
- die Operatoren sollten ein Boolean oder ein zu Boolean konvertierbaren Ausdruck zurückliefern
- ist ein Operator nicht definiert wird implizit die built-in Funktion id verwendet, die die Speicheradresse des Objektes als Vergleichkriterium verwendet
- Beispiel IdentityComparison
>>> class IdentityComparison(object):
... def __eq__(self,other):
... print "equal"
... return True
...
>>> a=IdentityComparison()
>>> b=IdentityComparison()
>>> a == b
equal
True
>>> a != b
True
>>> a < b
True
>>> a > b
False
>>> print id(a),id(b)
3085066604 3085068012
- comparision
- __cmp__
- der Operator sollte folgende Semantik für die Typen a,b besitzen
- a < b : -1
- a == b: 0
- a > b : 1
- Zusammenspiel:
- Das folgende Beispiel verdeutlicht das Zusammenspiel der rich mit der einfachen comparison.
- Beispiel Comparison:
>>> class Comparison(object):
... def __eq__(self,other):
... print "equal"
... return True
... def __cmp__(self,other):
... print "cmp"
... return 0
...
>>> a=Comparison()
>>> b=Comparison()
>>> a == b
equal
True
>>> a != b
cmp
False
>>> a < b
cmp
False
>>> a > b
cmp
False
- Die __eq__ Methode wird nur dann verwendet, wenn sie explizit definiert wird. Selbst __ne__ wird nicht auf __eq__ abgebildet.
Lebenszeit
Die zwei hook Methoden erlauben es, Aktionen beim Initialisieren und beim Löschen des Objekts zu hinterlegen.
>>> class Live(object):
... def __init__(self): print "hello"
... def __del__(self): print "good bye"
...
>>> for i in range( 10 ): a=Live()
...
hello
good bye
hello
good bye
hello
good bye
hello
good bye
hello
good bye
hello
good bye
hello
good bye
hello
good bye
hello
good bye
hello
good bye
Wieso wird __del__ nur einmal aufgerufen?
>>> a=Live()
hello
>>> b=a
>>> del a
>>> del b
good bye
Besonderheiten
Aufruf bei binären Operatoren
Die Magie des Python Interpreters besteht darin, ein Aufruf der Form a operation b auf a.operation(b)abzubilden.
>>> class Num(object):
... def __init__(self,n):
... self.__num= n
... def __add__(self,other): return self.__num + other
...
>>> five= Num(5)
>>> five + 3
8
>>> five.__add__(3)
8
- das bedeutet insbesondere, das bei binären Operatoren der linke Operand entscheidet, welcher Operator verwendet wird
>>> 3 + five
Traceback(most recent call last):
File "<stdin>", line 1, in <module>
- Python bietet für diese Fälle, in den der rechte Operarand die Wahl des Operatoren vorgeben soll, spezielle hook Methoden an, die mit dem Buchstaben r beginnen
- SymNum :
>>> class SymNum(object):
... def __init__(self,n): self.__num=n
... def __add__(self,other): return self.__num + other
... def __radd__(self,other): return other + self.__num
...
>>> four=SymNumb(5)
>>> four=SymNum(5)
>>> four=SymNum(4)
>>> four + 3
7
>>> 3 + four
7
- in dem Fall, daß beide Operanden die entsprechende Operation überladen haben, bindet der linke Operand stärker
- AddLeft und AddRight
>>> class AddRight(object):
... def __radd__(self,other): print AddRight.__name__
...
>>> class AddLeft(object):
... def __add__(self,other): print AddLeft.__name__
...
>>> AddLeft() + AddRight()
AddLeft
- Die Vergleichoperatoren können nicht rechtsgebunden aufgerufen werden. Der Pythoninterpreter bildet die Paare (>,<) und (>=,<=) symmetrisch aufeinander ab.
- Beispiel SymComp
>>> class SymComp(object):
... def __gt__(self,other):
... print "greater then"
... return True
... def __lt__(self,other):
... print "lower then"
... return True
...
...
>>> a=SymComp()
>>> a < 3
lower then
True
>>> 3 > a
lower then
True
>>> a > 3
greater then
True
>>> 3 < a
greater then
True
Semantik der Operatoren
Es gilt der einfache Grundsatz, daß die Semantik der Operatoren denen der built-inOperatoren entsprechen soll.
>>> class MyString(object):
... def __init__(self,s): self.__s= s
... def __add__(self,other): return float(self.__s)+float(other.__s)
...
>>> one= MyString("1")
>>> two=MyString("2")
>>> one+two
3.0
>>> ONE= str("1")
>>> TWO=str("2")
>>> ONE+TWO
'12'
Die Klasse MyString widerspricht der Absicht der Operator Überladung.
Sinn der Operator Überladung ist es, eigene Datentypen zu definieren, die sich wie built_in Datentypen verhalten.
Operator Überladung ist ein mächtiges, aber auch mit Vorsicht anzuwendendes Programmierwerkzeug, das kontrovers diskutiert wird. Kritik an Operator Überladung
Übungsaufgabe: Bruch
Entwerfe eine Klasse Bruch, die das Rechnen mit Brüchen umsetzt.
>>> Fraction(1,3) + Fraction(3,4)
13/12
>>> Fraction(1,3) * Fraction(3,4)
3/12
>>> Fraction(1,3) / Fraction(3,4)
4/9
>>> Fraction(1,3) - Fraction(3,4)
-5/12
>>> Fraction(1,2) ** 4
1/16
>>> print Fraction(1,2) ** 4
1/16
>>> sixteenthPart= Fraction(2,4) ** 4
>>> sixteenthPart
16/256
>>> sixteenthPart()
>>> sixteenthPart
1/16
>>> toFraction(10.125)
10125/1000
>>> print toFraction(10.125)
81/8
>>> Fraction(1,8) < Fraction(1,9)
True
>>> 3 < Fraction(7,5)
False
>>> print Fraction(1,8) + 3
25/8
>>> print 3 + Fraction(1,8)
25/8
>>> 1/Fraction(3,7)
70/30
>>> -1/Fraction(3,7)
-70/30
>>> -Fraction(3,7)
-3/7
>>> one=3*Fraction(1,3)
>>> one
30/30
>>> one()
>>> one
1/1
>>> one= 3*Fraction(1,3)
>>> print 3*Fraction(1,3)
1
>>> print ( 1+(3-Fraction(1,3))**2 )
73/9
- Unter Bruchrechnung findet ihr die bekannten Regeln zu Bruchrechnen.
- Ein paar Hilfsfunktionen um die Klasse Bruch zu vervollständigen
Die Funktione ggt ermittelt mit Hilfe der Primfaktorzerlegung den größten gemeinsamen Teiler zweier natürlicher Zahlen.
def ggt(first, second):
"get the ggt of first and second"
while second:
first, second = second, first%second
return first
Die Funktion toFraction wandelt eine float Zahl in einen Bruch um
def toFraction(fl):
"convert the float to an fraction"
#floating point decimal
fl=float(fl)
#get both parts of the divison
whole,frac= divmod( fl,1 )
# number of digits after the decimal point, rounded with str
num= len( str(frac).split(".")[1] )
#increase nominator and denominator
factor= pow(10,num)
return Fraction(int(whole*factor + frac*factor),int(factor))
Methodenbindung
Python Methoden können nicht nur an Objekte, sondern auch an Klassen gebunden werden. Darüber hinaus unterstützt Python statische Methoden, die an kein Objekt - sei es eine Klasse oder die Instanz einer Klasse - gebunden sind. Während statische Methoden in OO-Programmiersprachen wie Java und C++ auch angeboten werden, stellen Klassenmethoden ein neues Konzept dar.
Sowohl Klassenmethoden als auch statische Methoden setzen die sogenannten New-Style_Classes von Python voraus.
Objekt
Die Bindungen einer Methode geschieht über den Namen self. Die Methode kann nun als gebunden und ungebundene Methode aufgerufen werden.
>>> class Methode(object):
... def bound(self): print "bound"
... def unbound(self): print "unbound"
...
>>> a=Methode()
>>> a.bound()
bound
>>> Methode.unbound(a)
unbound
Klasse
Analog zu der Objektreferenz bei Objektmethoden müssen Klassenmethoden mit einer Klassenreferenz aufgerufen werden. Für diese Klassenreferenz hat sich die Namenskonvention cls entsprechend der Namenskonvention self für Instanzreferenzen etabliert.
>>> class ClassBase(object):
... def helloClass(cls): print "Hello from %s" % cls.__name__
... helloClass= classmethod( helloClass )
...
>>> class ClassDerived(ClassBase): pass
...
>>> hello= ClassBase()
>>> hello.helloClass()
Hello from ClassBase
>>> der=ClassDerived()
>>> der.helloClass()
Hello from ClassDerived
>>> ClassBase.helloClass()
Hello from ClassBase
>>> ClassDerived.helloClass()
Hello from ClassDerived
Eine Klassenmethode muß explizit deklariert werden. Beim Aufruf von helloClass findet die Klasse dynamisch heraus, von welchem Typ sie ist. Klassenmethoden können über die Instanz oder die Klasse aufgrufen werden.
Namensraum (statisch)
Im Gegensatz zu den Objekt- und Klassenmethoden sind statische Methoden an kein Objekt gebunden. Sie können ohne Objekt- oder Klasseninstanz aufgerufen werden. Ihr Unterschied zu freien Funktionen besteht darin, daß sie an den Namensraum der Klasse gebunden sind.
Statische Methoden bieten sich zum Schreiben von freien Funktionen an, da sie den globalen Namensraum nicht verschmutzen.
>>> class StaticBase(object):
... def helloStatic(): print "hello from StaticBase"
... helloStatic= staticmethod( helloStatic )
...
>>> class StaticDerived( StaticBase ): pass
...
>>> StaticBase.helloStatic()
hello from StaticBase
>>> StaticDerived.helloStatic()
hello from StaticBase
>>> base= StaticBase()
>>> base.helloStatic()
hello from StaticBase
>>> der= StaticDerived()
>>> der.helloStatic()
hello from StaticBase
Statische Methoden werden explizit deklariert. Sie können über den Klassenqualifier oder eine Instanz aufgerufen werden. Bei der Definition von StaticBase bzw. StaticDerived ist weder self noch cls notwendig.
Mit Python 2.4 wurde das mächtige Dekoratorkonzept eingeführt. Durch dies lässt sich eine Klassenmethode oder statische Methode eleganter deklarieren.
@classmethod
def helloClassMethod(cls):
...
@staticmethod
def helloStaticMethod():
...
- der Schöpfungsprozeß für Menschen hat sich verselbstständigt
>>> import random
>>> for i in range(10000):
... random.choice((Man,Woman))("anonymous")
...
.
.
.
<humanBeing.Man instance at 0xb7e2c5ec>
<humanBeing.Man instance at 0xb7e2c5cc>
<humanBeing.Man instance at 0xb7e2c5ec>
<humanBeing.Woman instance at 0xb7e2c5ec>
>>>
Wieviel Menschen, Frauen und Männer wurden geschaffen ? Erweitere die Klassenhierachie Menschen um die Mächtigkeit, über die Anzahl der Männer und Frauen Buch zu führen.Verwende dazu Klassenmethoden oder statische Methoden.
>>> import random
>>> for i in range(10000):
... random.choice((Man,Woman))("anonymous")
...
.
.
.
<humanBeing.Man instance at 0xb7e2c5cc>
<humanBeing.Woman instance at 0xb7e2c5ec>
>>> HumanBeing.mankind
{'Woman': 49851, 'Man': 50149}
>>> Man("Rainer")
<humanBeing.Man instance at 0xb7e2c5ec>
>>> HumanBeing.mankind
{'Woman': 49851, 'Man': 50150}
>>>
Introspektion
Attribute
Sowohl die Klasse Student als auch den Student schmitt können wir zu Laufzeit fragen, welche Attribute sie zur Verfügung stellen und wie diese Werte gesetzt sind. Dies erlaubt es uns ohne die Defition der Klasse mit den Datentype Student zu arbeiten.
Diese Introspektionsfähigkeit unterscheidet Python von Java und insbesondere C++
>>> dir( Student )
['__doc__', '__init__', '__module__', 'getID', 'getNumber', 'number', 'setID']
>>> Student.__dict__
{'__module__': 'Student',
'__init__': <function __init__ at 0x810dd8c>,
'getNumber': <function getNumber at 0x819cc0c>,
'setID': <function setID at 0x8191684>,'number': 4,
'getID': <function getID at 0x81a30b4>, '__doc__': None}
- die Attribute des Objekts
>>> schmitt=Student("Schmitt")
>>> dir( schmitt )
['_Student__ID', '_Student__name', '__doc__', '__init__', '__module__',
'getID', 'getNumber', 'number', 'setID']
>>> schmitt.__dict__
{'_Student__name': 'Schmitt', '_Student__ID': 'Schmitt1'}
Worin unterscheiden sich die Ausgaben der built-in Funktion dir und dem Objektdictionary __dict__ .
Klassen und Objekte werden in Python über Dictionaries implementiert
Objektmodell
Die folgende Klassenhierachie und Objektinstanzen setze ich im folgenden voraus.
class A(object): pass
class B(A): pass
class C(object): pass
a=A()
b=B()
c=C()
Typen
Typinformationen erhält man mit type bei Klassen und Objekten, aber auch bei built-in Typen.
>>> type(A) == type(B)
True
>>> type(a) == type(b)
True
>>> type(A)
<type 'type'>
>>> type(a)
<class '__main__.A'>
>>> type(5)
<type 'int'>
>>> type("")
<type 'str'>
Klassen
Mit issubclass kann die Klassenhierachie abgefragt werden.
>>> issubclass(B,A)
True
>>> issubclass(A,B)
False
>>> issubclass(A,A)
True
>>> issubclass(B,(C,A))
True
Objekte
Durch isinstance erhält man Information über alle Objekte.
>>> isinstance(a,A)
True
>>> isinstance(b,A)
True
>>> isinstance(b,C)
False
>>> isinstance(b,(C,B))
True
>>> import types
>>> isinstance(3,types.IntType)
True
>>> isinstance(3,types.FloatType)
False
Design
Beschränkt sich unsere Sicht auf objekt-orientiertes Programmieren im wesentlichen darauf, welche Werkzeuge Python zur Verfügung stellt um OO-Konzepte umzusetzen, werden wir uns in den weiterführenden Konzepten dem Design mit OO-Mitteln widmen.
Die folgenden Konzepte sind keine pythonspezifischen Konzepte.
Entwurf mit Klassen
Klassen erlauben auf vielfältige Art eigene Typen zu implementieren. Die Mächtigkeit des Klassenentwurfs besteht darin, neue Typen aus bestehenden Typen aufzubauen.
Dies kann, vereinfachend gesprochen, durch das Ableiten, das Zusammenstellen, das Delegieren und schließlich das Zusammenführenbestehender Typen erfolgen.
Ableitung is-a
Typbildung durch Ableitung wird auch gerne als Spezialisierung bezeichnet, da im Allgemeinen ein bestehender Typ eingeschränkt wird. Der abgeleitete, neue Typ ist daher auch ein Basistyp.
Um ein dictionary zu entwickeln, das nur einmalig initialisiert werden kann und danach konstant ist, bietet es sich an vom built-in Datentyp dict abzuleiten.
class ConstDict(dict):
"""ConstDict is a dictionary which could only once initilisized.
Afterward it's constant."""
def __init__(self, dictionary= None, **kwds):
if ( not self ): self.__initOnce(dictionary, **kwds)
def __setitem__(self, key, value): pass
def update(self , dictionary= None ,**kwds):
if ( not self ):self.__initOnce(dictionary,**kwds)
def __initOnce(self, dictionary= None ,**kwds):
if dictionary is not None:
dict.update(self,dictionary)
if ( len( kwds )):
dict.update(self,kwds)
Das dictionary verhält sich wie der built-in Datentyp dict.
Durch den Ausdruck (not None) wird sichergestellt, das MyDict nur einmal initialisiert werden kann. In ihm überprüfe ich, ob das Objekt tatsächlich initialisiert wurde. Dies geschieht durch den Konstruktoraufruf oder durch die update Methode.
Das explizite setzen bzw. modifizieren eines Wertes wird in der Implementierungsmethode __setitem__ ignoriert. Naheliegender ist es einen TypeError zu werfen statt den zu setzenden Wert nicht anzunehmen.
Die update Methode muß ich explizit überschreiben, da für sie keine interne Implementierungsfunktion ( __update__ ) existiert.
>>> a=ConstDict( {"4":"testMal"}, b=4 )
>>> import types
>>> isinstance( a, types.DictType )
True
>>> a
{'b': 4, '4': 'testMal'}
>>> a.update( b=4711)
>>> a
{'b': 4, '4': 'testMal'}
>>> c=ConstDict()
>>> c.update( {"4":"testMal"}, b=4 )
>>> c
{'b': 4, '4': 'testMal'}
>>> c["b"]=4711
>>> c
{'b': 4, '4': 'testMal'}
In Anwendungsbereichen, in denen es eine hierachische Strukturierung der Typen gibt, bietet sich Ableitung zum Modellieren des Gesamtentwurfes an. Insbesondere in GUI-Bibliotheken drängt sich diese Sicht auf. Übersteigt die Ableitungstiefe 3 oder 4 Stufen, wird er Entwurf zunehmend komplex.
Hier setzt die Idee an, Untertypen als Zusammenstellung bestehender Typen zu modellieren.
Kompositum has-a
Das Kompositum besitzt - has-a - seine Untertypen, während die abgeleitete Klasse insbesondere - is-a - eine Basisklasse ist. Die abgeleitet Klasse besitzt das Interface der Basisklasse, das Kompositum hingegen schafft ein neues Interface.
Bei vielen Abfragen eines Dictionaries ist von Interesse, nicht nur die Zuordnung Schlüssel Wert in konstanter Zeit aufzulösen, sondern auch die dazu inverse Abfrage. Leider ist beim Standard Dictionary der Zugriff vom Wert zu Schlüssel linear abhängig von der Länge des Dictionaries.
MultiSymDict stellt ein Dictionary dar, in dem nicht nur die Zuordnung Schlüssel Wert, sondern auch die Zuordnung Wert Schlüssel als Dictionaryzugriff möglich ist. Baut man eine solche Struktur aus zwei dictionaries auf, stellt sich bald das Problem, das die Zuordnung Wert Schlüssel nicht eindeutig ist. Daher werden ich die Werte, seien es nun die ursprünglichen Werte oder Schlüssel, durch Listen repräsentieren.
Für jede Sicht auf das MulitSymDict verwende ich ein eigenes Dictionary. Modifikationen auf MulitSymDict werden auf die Untertypen automatisch abgebildet.
class MultiSymDict(object):
"""MultiSymDict is a symmetric dictionary with multi values;
MultiSymDict holds two dictionaries;
first: the classical key -> value dictionary;
second: the value -> key dictionary;
you can init it with a dictionary
with a sequence of pairs"""
def __init__(self,pairs= None ):
self.__first= {}
self.__second= {}
if (pairs): self.__addToDict(pairs)
def add(self, pairs):
self.__addToDict(pairs)
def remove(self, pairs):
self.__removeFromDict(pairs)
def change(self, origPairs, newPairs):
self.__removeFromDict(origPairs)
self.__addToDict(newPairs)
def first(self): return self.__first
def second(self): return self.__second
def __addToDict(self, pairs):
if (type(pairs) in ( type( [] ),type( (0,0) ) )):
self.__assertionPairs(pairs)
self.__addPairs(pairs)
elif (type(pairs) == type({})):
self.__addPairs(pairs.items())
def __removeFromDict(self, pairs ):
if ( type( pairs ) in ( type([]),type((0,0)))):
self.__assertionPairs(pairs)
self.__removePairs(self.__first, pairs)
self.__removePairs(self.__second ,map( lambda i: ( i[1],i[0] ), pairs ) )
elif ( type(pair ) == type({})):
self.__removePairs( self.__first,pairs.items() )
self.__removePairs( self.__second ,map( lambda i: ( i[1],i[0] ), pairs.items() ) )
def __addPairs( self, pairs ):
self.__first= self.__generateMultiMapFromPairs( self.__first, pairs )
self.__second= self.__generateMultiMapFromPairs( self.__second ,
map( lambda i: ( i[1],i[0] ), pairs ))
def __removePairs(self,dic,pairs):
for pair in pairs:
seq= dic[pair[0]]
seq.remove( pair[1] )
if ( not seq ): dic.pop(pair[0])
def __generateMultiMapFromPairs( self, dic, pairs ):
for pair in pairs:
if dic.has_key(pair[0]): dic[pair[0]].append(pair[1])
else: dic[pair[0]]= [pair[1]]
return dic
def __assertionPairs( self, pairs ):
dict( pairs )
MultiSymDict läßt sich entsprechend einem Dictionary initialisieren. Darüber hinaus können weitere Dictionarys hinzugefügt, entfernt und ausgetausch werden. Die Unterdictionaries self.__first und self.__second habe ich mit zwei öffentlichen Methoden gekapselt, um die direkte Manipulation dieser Datentypen deutlich zu erschweren.
>>> myDict= MultiSymDict([(1,2),("a","b"), ("1",2)])
>>> myDict.first()
{'a': ['b'], 1: [2], '1': [2]}
>>> myDict.second()
{2: [1, '1'], 'b': ['a']}
>>> myDict= MultiSymDict({1:2,"a":"b","1":2})
>>> myDict= MultiSymDict(zip((1,"a","1"),(2,"b",2)))
- Anwendung: Worthäufigkeit in einem String
- die Funktion wordCount zählt die Häufigkeit der Wörter in einem String
import re
re_word= re.compile(r"(\w+)")
def wordCount(s):
allParts= re_word.split(s)
wordDict= {}
for word in allParts[1::2]:
wordDict[word]= wordDict.setdefault(word,0) + 1
return wordDict
- wende ich diese Funktion an, ergibt sich das erwartete Ergebnis
>>> import urllib
>>> wordCount(urllib.urlopen("http://www.heise.de").read())
...
, 'Phisher': 1, 'Komponenten': 2, 'Zugang': 2, 'var': 3, 'function': 1,
'size120': 1, 'Der': 7, 'delivery': 4, 'meldet': 1, 'Monate': 1, 'ho': 20,
'Den': 1, 'he': 3, 'Kaesten': 1, 'seit': 2, 'Radiosender': 1,
'menschliche': 1, 'CDATA': 1, 'sein': 1, '728': 2, 'braucht': 1, 'SRC': 7,
'innerhalb': 1, '4251454a44774d4568362f6273513d3d': 1, 'resale': 3,
'Nachricht': 1, '46': 2, '45': 7, '42': 1, 'detail': 1, 'mittelstandsblog': 1,
'oas': 17, 'sich': 2, 'Banner': 1, 'strengeren': 1, 'Zeittarif': 1,
'Identifizierung': 1, 'Demo': 1, 'Site': 2, 'write': 10, 'gewordene': 1}
>>>
- initialisiere ich MultiSymDict mit der Ausgabe des letzten Ausdruckes, dann besitze ich ein wesentlich mächtigeres Interface auf das initiale Dictionary
>>> import urllib
>>> heiStat= MultiSymDict( wordCount(urllib.urlopen("http://www.heise.de").read()) )
>>> heiStat.first()
...
'45': [1], '42': [1], 'detail': [1], 'mittelstandsblog': [1], 'oas': [19], 'sich': [3],
'u': [1], 'Banner': [1], 'strategische': [1], 'strengeren': [1], 'Zeittarif': [1],
'Bildschirmschoner': [1], 'Identifizierung': [1], 'Demo': [1], 'Site': [1],
'write': [10], 'hinzuf': [1]}
>>> heiStat.second()
...
38: ['table'], 39: ['und'], 40: ['www'], 171: ['href'], 44: ['img', 'option', 'span'],
48: ['src', 'border'], 50: ['width', 'http', 'h3'], 51: ['tr'], 53: ['1'],
54: ['meldung'], 138: ['p'], 64: ['heise'], 66: ['newsticker'], 69: ['de'], 75: ['br'],
82: ['b'], 89: ['0'], 166: ['class'], 317: ['a']}
>>> print sorted(heiStat.first().items() )
...
, ('wobei', [1]), ('write', [10]), ('wurde', [1]), ('www', [40]), ('wwwheise', [19]),
('wwww', [2]), ('xml', [3]), ('zaehler', [6]), ('zahlen', [1]), ('zeitschriften', [1]),
('zivilisierten', [1]), ('zivilrechtlichen', [1]), ('zoneid', [2]), ('zu', [10]),
('zudem', [1]), ('zum', [4]), ('zur', [1]), ('zusammen', [3]), ('zuvor', [1]),
('zwischen', [3])]
>>> print sorted( heiStat.second().keys())
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25,
26, 27, 29, 30, 32, 35, 36, 38, 39, 40, 44, 48, 50, 51, 53, 54, 64, 66, 69, 75, 82, 89,
128, 138, 166, 171, 317]
>>> print heiStat.second()[18]
['_blank', 'navi_links']
>>> print sorted( heiStat.second().items() )
...
['ads']), (35, ['gif']), (36, ['title', 'alt']), (38, ['table']), (39, ['und']),
(40, ['www']), (44, ['img', 'option', 'span']), (48, ['src', 'border']),
(50, ['width', 'http', 'h3']), (51, ['tr']), (53, ['1']), (54, ['meldung']),
(64, ['heise']), (66, ['newsticker']), (69, ['de']), (75, ['br']), (82, ['b']),
(89, ['0']), (128, ['td']), (138, ['p']), (166, ['class']), (171, ['href']), (317, ['a'])]
Delegation
Die Delegation ist dem Kompositum sehr ähnlich. Während in dem Kompositum das Zusammenstellen von Typen aus Subtypen unter dem Strukturaspekt betrachtet wird, liegt der Blickwinkel bei der Delegation auf dem Verhalten des neuen Types. Die Klasse delegiert Aufgaben an ihre Subtypen, die sie dann in ihrer spezifischen Form erfüllen.
Die Delegation als Strukturmittel bietet sich insbesondere bei Typen an, in denen sich Strategien zur Arbeitung von Unteraufgaben identifizieren lassen.
class Car(object):
"""get the price from a assembled car"""
def __init__(self, wh, mo, bo ):
self.wheel= wh
self.motor=mo
self.body=bo
def getCar(self):
print "Offer : " + str(self.__getPrice())
print self.__getName()
print
def __getPrice(self):
return 4*self.wheel.getPrice() + self.motor.getPrice() + self.body.getPrice()
def __getName(self):
return """assembled from Wheels: %s
Motor : %s
Body : %s """%( self.wheel.getName(), self.motor.getName(), self.body.getName())
class CarPart(object):
@classmethod
def getName(cls): return cls.__name__
class VWwheel(CarPart):
def getPrice(self): return 100
class VWmotor(CarPart):
def getPrice(self): return 500
class VWbody(CarPart):
def getPrice(self): return 850
class BMWwheel(CarPart):
def getPrice(self): return 300
class BMWmotor(CarPart):
def getPrice(self): return 850
class BMWbody(CarPart):
def getPrice(self): return 1250
class Trabiwheel(CarPart):
def getPrice(self): return 30
class Trabimotor(CarPart):
def getPrice(self): return 350
class Trabibody(CarPart):
def getPrice(self): return 550
vw= Car(VWwheel(),VWmotor(),VWbody())
bmw= Car(BMWwheel(),BMWmotor(),BMWbody())
trabi= Car(Trabiwheel(),Trabimotor(),Trabibody() )
def assembleCars( num ):
"assemble randomly num cars"
import random
wheels= (VWwheel(),BMWwheel(),Trabiwheel())
motors= (VWmotor(),BMWmotor(),Trabimotor())
bodies= (VWbody(),BMWbody(),Trabibody())
for i in range(num):
randomCar= Car(random.choice(wheels),random.choice(motors),random.choice(bodies ))
randomCar.getCar()
Der Typ Car setzt sich aus drei Untertypen wheel, motor und body zusammen. Jeder dieser Untertypen weiß, wie es die spezifischen Anfragen bezüglich des Namens und Preises zu beantwortet hat. Car delegiert die Aufrufe an seine Bestandteile und stellt sie dar.
>>>>>> bmw.getCar()
Offer : 3300
assembled from Wheels: BMWwheel
Motor : BMWmotor
Body : BMWbody
>>>>>> vw.getCar()
Offer : 1750
assembled from Wheels: VWwheel
Motor : VWmotor
Body : VWbody
>>>>>> trabi.getCar()
Offer : 1020
assembled from Wheels: Trabiwheel
Motor : Trabimotor
Body : Trabibody
>>>>>> # assemble a scCar
...
>>>>>> scCar= Car( VWwheel(), BMWmotor(), Trabibody() )
>>>>>> scCar.getCar()
Offer : 1800
assembled from Wheels: VWwheel
Motor : BMWmotor
Body : Trabibody
>>>>>> # assemble randomly cars
...
>>>>>> assembleCars( 4 )
Offer : 2550
assembled from Wheels: BMWwheel
Motor : VWmotor
Body : VWbody
Offer : 2100
assembled from Wheels: VWwheel
Motor : BMWmotor
Body : VWbody
Offer : 2950
assembled from Wheels: BMWwheel
Motor : VWmotor
Body : BMWbody
Offer : 2220
assembled from Wheels: Trabiwheel
Motor : BMWmotor
Body : BMWbody
Die Erzeugung von neuen Typen durch Komposition von Untertypen und die Delegation der Strategien an diese Untertypen ist wesentlich mächtiger als die Bildung von Untertypen durch Vererbung. Dies betrifft im wesentlichen zwei Aspekte:
- Definition der Untertypen:
- Delegation zu Laufzeit (dynamisch)
- Unterklassenbildung beim Source schreiben (statisch)
- kombinatorische Betrachtung der Unterklassenanzahl (am Beispiel Car)
- Delegation : Basisklasse + 3 * ( wheel + motor + body ) = 10
- Ableitung: Basisklasse + 3*wheel * 3*motor * 3*body= 28
Die Delegation als Strukturprinzip, um Verhalten zu modellieren, ist unter dem Namen StrategyPattern oder Policy Based Design ein feststehender Begriff.
Mehrfachvererbung mix-in
Während die bisherigen Klassenmodellierungen mit den Standard OO Konstrukten möglich waren, setzen die mix-in Klassen Mehrfachvererbung voraus. Die Grundidee besteht darin, eine neue Klasse zu erzeugen, die aus mehreren Basisklassen zusammengemischt wird.
Das klassische Beispiel ist ein http Server, der eine statische Seite an den Aufrufer übermittelt. Beim http Server registriert man in diesem Fall einen Handler, der auf http-GET Anfragen reagiert. Mein Handler rechnet die Zeit bis zum Ende der Python Schulung um 17:00 aus und gibt eine html-Seite zurück.
import BaseHTTPServer
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
import datetime
import os
import threading
actTime= datetime.datetime.now()
endTime= datetime.datetime(datetime.datetime.now().year, datetime.datetime.now().month ,
datetime.datetime.now().day ,17)
diffSeconds= (endTime-actTime).seconds
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write("""<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Wie lange dauerts noch?</title>
</head>
<body>
<h1 align="center"><font size="10" color="#FF0000">Count down</font></h1>
<p align="center"> <font size="6"> %s Sekunden noch bis zum Ende der Python Schulung.</font> </p>
<p align="right"> <font size="4"> <br> <br> <br> <br> served by(process,threads):(%s,%s )</font> </p>
</body>
</html>""" %(str(diffSeconds),os.getpid(),str(threading.enumerate()) ))
Um ihn zu nutzen, muß ich noch die Anwendungslogik beim http Server registrieren und diesen starten.
>>> import BaseHTTPServer
>>> seqServ = BaseHTTPServer.HTTPServer(("",4711),RequestHandler)
>>> seqServ.serve_forever()
localhost - - [22/Jul/2007 00:02:51] "GET / HTTP/1.1" 200 -
localhost - - [22/Jul/2007 00:03:02] "GET / HTTP/1.1" 200 -
BaseHTTPServer.HTTPServer(("",4711),RequestHandler) ist ein einfacher sequentieller Server. Er nimmt einen Request an, macht für diesen einen neuen Handler auf und bearbeitet in diesem Handler den Request.
Um den Durchsatz des Server zu erhöhen, ist es naheliegend, die Handler in eigenen Prozessen oder Threads zu erzeugen. Der Server muß dann lediglich noch sequentiell die Request entgegennehmen, ist aber dafür von der zeitaufwändigen Bearbeitung des Request frei.
Durch das hinzumischen eines ForkingMixIn bzw. ThreadingMixIn Klasse kann unser sequentieller Server parallelisiert werden.
>>> class ForkServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer): pass
...
>>> forkServer= ForkServer(("",4712),RequestHandler)
>>> forkServer.serve_forever()
localhost - - [22/Jul/2007 00:22:47] "GET / HTTP/1.1" 200 -
...
>>> class ThreadServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): pass
...
>>> threadServer= ThreadServer(("",4713),RequestHandler)
>>> threadServer.serve_forever()
localhost - - [22/Jul/2007 00:29:31] "GET / HTTP/1.1" 200 -
...
Die feinen Unterschiede der Server sieht man in der Statuszeile served by (process,threads) der HTTP Datei sehr gut.
Servertyp | Prozeß ID | aktive Threads | Beschreibung |
BaseHTTPServer |
identisch |
ein Thread |
alle Request werden sequentiell abgearbeitet |
ForkServer |
verändert sich |
ein Thread |
für jeden Request wird ein neuer Prozeß erzeugt |
ThreadServer |
identisch |
mindesten ein Thread |
für jeden Request wird ein neuer Thread erzeugt |
Wie funktioniert nun das ganze?
Alle drei Klassen, BaseHTTPServer.HTTPServer, SocketServer.ForkingMixIn und SocketServer.ForkingMixIn sind Unterklassen von SocketServer. Um eine Clientanfrage in einem Requesthandler zu beantworten, rufen sie jeweils ihre spezifische Methode process_request() auf. Diese Methode ruft den Requesthandler nun im gleichem, in einem neuen Prozeß oder ein einem Thread auf. Durch das Voranstellen des SocketServer.ThreadingMixIn in der Klassendefinition von
class ThreadServer (SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer)
wird die process_request() Methode des ersten Basisklasse verwendet.
Dies Verhalten kann leicht nachgestellt werden.
>>> class HTTPServer(object):
... def process_request(self):
... print self.__class__.__name__
...
>>> class ThreadingMixIn(object):
... def process_request(self):
... print self.__class__.__name__
...
>>> class ThreadingServer(ThreadingMixIn, HTTPServer): pass
...
>>> class OneProcessServer(HTTPServer, ThreadingMixIn): pass
...
>>> thr= ThreadingServer()
>>> one= OneProcessServer()
>>> thr.process_request()
ThreadingServer
>>> one.process_request()
OneProcessServer
Mix-in Klassen in dieser Form sind eine Besonderheit von Python, den zum Beispiel in C++ ist es ein Syntaxfehler, Methoden mehrfach in der gleichen Ableitungshierachien anzubieten.
Ausblick
Gegenüberstellung von C++, Java und Python
Ein paar interessanten Aspekte, in denen sich die drei objektorientierten Programmiersprachen unterscheiden.
Eigenschaft | C++ | Java | Python |
explizite Instanzbindung |
|
|
|
Klassenmethoden |
|
|
|
Überladen von Methoden |
|
|
|
Mehrfachvererbung |
|
|
|
Operator Überladung |
|
|
|
Zugriffskontrolle |
|
|
|
Zugriffskontrolle der Basisklassen |
|
|
|
Virtualität |
|
|
|
Interfaces |
|
|
|
Garbage Collection |
|
|
|
Multiparadigmen Programmiersprache |
|
|
|
Nach dieser tabellarischen Gegenüberstellung folgen nun die Details.
Kapselung
Operator Überladung
Operator Überladung erlaubt es, eigene Datentypen mit built-in Verhalten zu implementieren.
C++ und insbesondere Python besitzen sehr mächtige Operator Überladungs Konzepte. Selbst Funktionen sind in Python nur Objekte, auf denen spezifische Methoden überladen sind. Java unterstützt kein Operator Überladung, sieht man von Strings ab.
Operator Überladung ist in Python ein wichtiges Architekturmittel um das Interface von der privatenImplementierung zu trennen.
Zugriffskontrolle
Variablen und Methoden
Python bietet im Gegensatz zu C++ und Java keine Zugriffskontrolle auf seine Attribute an. Das Verbergen der Attribute einer Klasse vor der Aussenwelt mittels privat und die Sichtbarmachung der Klasseninternas für abgleitete Klassen mittels protected kennt Python nicht.
Python verwendet pseudo-private Attribute. Dies sind Attribute, die mit zwei Unterstrichen __ beginnen. Diese Attribute werden gemangelt, damit sie nicht mehr so einfach ansprechbar sind.
>>> class NameMangling(object):
... def __privateMethode(self): pass
...
>>> nm= NameMangling()
>>> dir( nm )
['_NameMangling__privateMethode', '__doc__', '__module__']
>>> nm._NameMangling__privateMethode()
Das Name mangling mit einem führenden Unterstrich und dem Klassennamen ist in dieser Form definiert, so daß man von aussen auf die privatenAttribute eines Objekts in definierter Weise zugreifen kann.
Basisklassen
C++ Klassen werden per default von der Basisklasse private abgeleitet. Dies bedeutet, das nur die public Methoden und Variablen in der abgeleiteten Klasse sichtbar sind.
Im folgenden beschränke ich mich auf die Methoden der Klasse.
- Durch eine public Basisklasse, werden public und protected Methoden zu public und protected Methoden in der abgeleiteten Klasse.
- Durch eine protected Basisklasse, werden public und protected Methoden zu protected Methoden in der abgeleiteten Klasse.
- Durch eine private Basisklasse, werden public und protected Methoden zu privaten Methoden in der abgeleiteten Klasse.
Zur Veranschaulichung soll die folgende Klassenstruktur dienen.
- Child leite ich public von A, protected von B und private von C ab.
- GrandChild ist eine öffentliche Unterklasse von Child.
- Jede Basisklasse bietet drei Methoden jeweils public, protected und private an.
#include <iostream>
#include <string>
class A{
public: std::string getAPublic() { return "APublic "; }
protected: std::string getAProtected() { return "AProtected ";}
private: std::string getAPrivate() { return "APrivate ";}
};
class B{
public: std::string getBPublic() { return "BPublic "; }
protected: std::string getBProtected() { return "BProtected ";}
private: std::string getBPrivate() { return "BPrivate ";}
};
class C{
public: std::string getCPublic() { return "CPublic "; }
protected: std::string getCProtected() { return "CProtected ";}
private: std::string getCPrivate() { return "CPrivate ";}
};
class Child: public A, protected B, private C {
public:
std::string inChild(){
return getAPublic() + getAProtected() + "\n" +
getBPublic() + getBProtected() + "\n" +
getCPublic() + getCProtected();
}
};
class Grandchild: public Child{
public:
std::string inGrandChild(){
return getAPublic() + getAProtected() + "\n" +
getBPublic() + getBProtected();
}
};
int main(){
Child child;
std::cout << "--------- in child.inChild-------------- " << std::endl;
std:: cout<< child.inChild() << std::endl;
Grandchild grandChild;
std::cout << "-------- in grandChild.inGrandChild------ " << std::endl;
std::cout << grandChild.inGrandChild() << std::endl;
std::cout << "------------ from child ----------------- " << std::endl;
std::cout << child.getAPublic() << std::endl;
std::cout << "---------- from grandChild -------------- " << std::endl;
std::cout << grandChild.getAPublic() << std::endl;
};
Die maximale Sichbarkeit auf die Methoden der Basisklasse ergibt folgende Ausgabe:
--------- in child.inChild--------------
APublic AProtected
BPublic BProtected
CPublic CProtected
-------- in grandChild.inGrandChild------
APublic AProtected
BPublic BProtected
------------ from child -----------------
APublic
---------- from grandChild --------------
APublic
Durch public inheritance ist die abgeleitete Klasse immer auch eine Basisklasse und kann daher im gleichen Kontext wie die Basisklasse verwendet werden. Die abgeleitete Klasse is_a Basisklasse.
Die öffentlich abgeleitete Klasse leitet das Interface ab, während die privat abgeleitete Klasse die Implementierung ableitet.
Instanzen der privat abgeleiteten Klasse können weder das Interface der Basisklasse nützen noch sind sie interfacekompatibl zur Basisklasse.
class Base{};
class Derived: private Base{};
void allowOnlyBase( const Base& base ){};
int main(){
const Derived& d= Derived();
allowOnlyBase(d);
}
Dieser Code führt beim Übersetzen zu der erwarteten Fehlermeldung, da Derived nicht interfacekompabil zu Base ist.
Fehler: Base ist eine nicht erreichbare Basis von Derived
Eine typische Anwendung für das private Ableiten ist das AdapterPattern. Ziel des Musters ist es, eine bestehende Klasse mit einem neuem Interface auszustatten. Dazu leitet der Programmierer public von dem neuem Interface und private von der ursprünglichen Klasse ab.
Mehrfachvererbung
In Python wird Mehrfachvererbung gerne in Form von mix-in Klassen verwendet. Der Pythoninterpreter geht nach dem Prinzip first-match vor, um sein Attribut aufzulösen. Im Gegensatz hierzu werden alle Methoden einer Ableitungshierachie in C++ als gleichwertig angesehen. Daher können in C++ die Methoden nicht eindeutig aufgelöst werden - insofern sie die gleiche Signatur besitzen - und führen zum Abbruch des Compilevorgangs.
>>> class BaseA(object):
... def name(self): print "BaseA"
...
>>> class BaseB(object):
... def name(self): print "BaseB"
...
>>> class Derived(BaseA,BaseB): pass
...
>>> d=Derived()
>>> d.name()
BaseA
- das äquivalente Programm in C++
#include <iostream>
class BaseA{
public:
void name() const {
std::cout << "BaseA" << std::endl;
}
};
class BaseB{
public:
void name() const {
std::cout << "BaseB" << std::endl;
}
};
class Derived: public BaseA, public BaseB {};
int main(){
Derived d;
d.name();
}
- erzeugt nicht das gewünsche Ergebnis
grimm@ackbar test $ g++ derived.cpp
derived.cpp: In function 'int main()':
derived.cpp:23: error: request for member 'name' is ambiguous
derived.cpp:12: error: candidates are: void BaseB::name() const
derived.cpp:5: error: void BaseA::name() const
Interfaces
Ein Interface ist im objektorientierten Entwurf eine spezielle Basisklasse, die die Schnittstelle für alle von ihr abgeleiteten Klassen vorgibt. In Java bestehen Interfaces aus Methodendeklaration und Konstanten. Interfaces sind Schnittstellendefinitionen, die in allen abgeleiteten Klassen implementiert werden müssen. Erst die abgeleiteten Klassen können instanziiert werden.
public interface HumanBeing {
String getSex();
}
public class Man implements HumanBeing {
public String getSex() {
return "male"
}
}
Weder C++ noch Python unterstützen Interfaces.
C++ und Python 3.* kennen das Konzept der rein virtuellen Methode. Klassen, die rein virtuelle Methoden enthalten, können nicht instanziiert werden und werden insofern als abstrakte Klassen bezeichnet. Abstrakte Klassen, die auch von Java angeboten werden, gehen über die reine Deklaration hinaus.
#include <string>
#include <iostream>
class HumanBeing{
public:
virtual std::string getSex()=0;
std::string decorateMan(){
return "I'm ";
}
};
class Man: public HumanBeing{
public:
std::string getSex() {
return decorateMan() + "male";
}
};
int main(){
HumanBeing* hb=new Man();
std::cout << hb->getSex() << std::endl;
}
- erzeugt die Ausgabe
I'm male
Die Vorgabe, die Interfaces oder abstrakten Klassen an die abgeleitete Klasse setzen, ist ein sehr wichtiges Entwurfsprinzip. Klassen werden gegen das Interface programmiert.
Das Interface ist der stabile Faktor im Enwurf, während die Implementierung ausgetauscht werden kann.
interface Instrument {
void play();
}
class Percussion implements Instrument {
public void play() {
System.out.println("Percussion.play()");
}
}
class Brass implements Instrument {
public void play() {
System.out.println("Brass.play()");
}
}
public class music{
public static void main(String[] args) {
Instrument ins= new Percussion();
ins.play();
ins= new Brass();
ins.play();
}
}
Percussion.play()
Brass.play()
Dieser Interface orientierten Entwurf - Implementiere gegen das Interface - der statisch typisierten Sprachen, steht dem duck-typing Entwurf der dynamisch typisierten Sprachen - wie Python - entgegen:
If it walks like a duck and quacks like a duck, I would call it a duck.
Polymorphie
In Python und Java sind Methoden immer virtuell. In beiden Sprachen wird erst zu Laufzeit des Programms bestimmt, von welchem Typ das Objekt ist. Dies Verhalten wird gerne späte oder dynamische Bindung genannt und muss in C++ explizit über das Schlüsselwort virtual bei der Methodendeklaration ausgedrückt werden. Falls die Methode nicht als virtual deklariert ist, besitzt sie den statischen Typ zur Compilezeit.
Die Klasse Base und Derived sollen den Unterschied zwischen virtuell und nicht virtuell, bzw. zwischen später und früher Bindung verdeutlichen.
#include <iostream>
class Base{
public:
virtual std::string getVirtual() const { return "Base"; }
std::string getNotVirtual() const { return "Base"; }
};
class Derived: public Base{
public:
virtual std::string getVirtual() const { return "Derived"; }
std::string getNotVirtual() const { return "Derived"; }
};
int main(){
Base* b=new Derived();
std::cout << "b->getVirtual() : " << b->getVirtual() << std::endl;
std::cout << "b->getNotVirtual() : " << b->getNotVirtual() << std::endl;
}
Abhängig von der Virtualität der Methode wird die Methode aus Base oder Derived aufgerufen:
b->getVirtual() : Derived
b->getNotVirtual() : Base
Erweiterungen des OO-Konzepts
Multiparadigmen Programmierung
Mit Einschränkungen von Generics, die Java seit 1.5 unterstützt, ist Java streng objekt-orientiert. Java kennt keine freien Funktionen, so daß main als Einstiegssprung in das Programm eine statische Methode sein muß.
C++ und Python werden als multiparadigmen Programmiersprachen bezeichnet. Neben dem prozeduralen prozeduralen unterstützen sie das funktionale und das aspektorientierte Programmierparadigma.
Python erweitert die objektorientierte Programmierung um das Konzept Klassen zu instanziieren. ( metaclass programming)
Die C++ Umsetzung der generischen Programmierung mit Templates, ermöglicht darüber hinaus das generativeProgrammierparadigma.
Klassenmethoden
Python bietet seit Einführung des Descriptorprotokolls Klassenmethoden. Dies sind im Gegensatz zu Instanzmethoden Methoden, die an die Klasse gebunden sind. Klassenmethoden können mit einer Klasseninstanz aufgerufen werden. Statische Methoden, die auch Java und C++ kennen, werden auf dem Klassenqualifier aufgerufen, bedürfen daher keiner Klasseninstanz.
Python kennt drei Arten von Methodenbindungen, die an die Instanz, die Klasse oder den Namensraum der Klasse (statisch).
Besonderheiten
Garbage Collection
Sowohl Python als auch Java unterstützen Garbage Collection .
Java verwendet in der Regel tracing collectors zur Speicherbereinigung. In diesem Verfahren werden von den aktiven Objekten ausgehend alle Objekte markiert, die erreicht werden können. Für die weitere Vorgehensweise existieren zwei Alternativen:
- mark and sweep: alle Objekte, die nicht erreicht werden können, werden gelöscht
- mark and compact: Objekte, die erreicht erreicht werden können, werden kompakt zusammenkopiert, so daß die verbleibenden Objekte gelöscht werden können
Beide Algorithmen können zirkuläre Referenzen auflösen. Mit mark und compact wird darüberhinaus noch der Speicher defragmentiert. Der tatsächlich verwendete Speicherfreigabealgorithmus hängt von der Implementierung der Java Virtual Machine ab.
Python verwendet einen reference countingAlgorithmus zur Speicherfreigabe. Dieser Algorithmus zeichnet sich dadurch aus, das jedes Objekt mitzählt, wie oft es referenziert wird. Fällt der Referenzzähler auf 0, kann das Objekt gelöscht werden. Pythons Implementierung des Referenzzählers entdeckt auch zyklische Referenzen.
explizite Instanzbindung
Die Zugehörigkeit der Attribute zu einem Objekte wird in Python explizit über den Bezeichner self gesetzt. Dabei bezeichnet self kein Schlüsselwort sondern eine Namenskonvention. self referenziert die Objektidentität.
Entscheidend für die Objektreferenz ist nicht der Namen, sondern die Position des Names bei der Definition der Methode. Ob ich nun die Objektidentität mit self oder rainer bezeichne, tatsächlich wird immer das gleiche Objekt referenziert.
>>> class MyClass(object):
... def __init__(self):
... self.test=5
... print id(self)
... def printMe(rainer):
... rainer.test=5
... print id(rainer)
...
>>> a=MyClass()
3085067564
>>> a.printMe()
3085067564
>>> print id( a)
3085067564
>>> a.test
5
Entsprechend der Namenskonvention self für die Instanzmethoden, hat sich cls für Klassenmethodenetabliert.
Überladen von Methoden
Überladen von Methoden bezeichnet die Möglichkeit, mehrere gleichnamige Methoden in einer Klasse mit verschiedenen Parameterlisten (Signaturen) zu definieren. Zur Laufzeit des Programms entscheidet dann der Compiler oder Interpreter, welche Methode am besten passt.
Da Python die Methoden und Attribute zu einer Instanz in einem Instanzdictionary hält, und der Name der Methode oder des Attributes den Schlüssel im Dictionary darstellt ist der letzte Eintrag in das Dictionary der gültige.
Kurz und gut, Python unterstützt kein Überladen von Methoden.
>>> class MethodenUeberladung(object):
... test=5
... def test(self, a): print "one"
... def test(>>> class MethodenUeberladung(object):
... test=5
... def test(self, a): print "one"
... def test(self): print "zero"
...
>>> a=MethodenUeberladung()
>>> dir( a )
['__doc__', '__module__', 'test']
>>> a.test()
zero
Weiterlesen...