Einfache Architektur
Objektorientiertes Programmieren mit Python
Numerische Typen
Es existieren zahlreiche Methoden [6], die Ihnen dabei helfen, neue numerische Datentypen (wie Pythons eingebaute Typen int, complex, aber auch in Modulen definierte wie datetime.timedelta) zu erzeugen. Die Beispiel-Klasse in Listing 5 enthält diverse Methoden zum Verarbeiten von zweidimensionalen Vektoren.
# coding: iso-8859-1
import math
class Vektor(object):
def __init__(self, x=0.0, y=0.0):
self.x = float(x)
self.y = float(y)
self._cls = self.__class__
def __pos__(self):
return self._cls(self.x, self.y)
def __neg__(self):
return -1.0 * self
def __add__(self, rechte_seite):
return self._cls(self.x+rechte_seite.x, self.y+rechte_seite.y)
def __sub__(self, rechte_seite):
return self + (-rechte_seite)
def __mul__(self, rechte_seite):
try:
rechte_seite.x
except AttributeError:
# rechte_seite ist ein Skalar
skalar = rechte_seite
return self._cls(skalar * self.x, skalar * self.y)
else:
# rechte_seite ist ein Vektor
return self.x*rechte_seite.x + self.y*rechte_seite.y
def __rmul__(self, linke_seite):
return self * linke_seite
def __abs__(self):
return math.sqrt(self * self)
def __repr__(self):
return "Vektor(%g, %g)" % (self.x, self.y)
def __str__(self):
return "(%g, %g)" % (self.x, self.y)
Die Klasse greift in möglichst vielen Methoden auf die Hilfe von anderen Methoden der Klasse zurück. Zum Beispiel subtrahiert die Klasse zwei Vektoren, indem zum ersten Vektor der negierte zweite Vektor hinzukommt. Durch diesen Ansatz sparen Sie viel redundanten Code, wodurch weniger Fehler auftreten.
Die Methode __init__ in den Zeilen 6 bis 9 erlaubt die optionale Angabe der x- und y-Koordinaten. Sind diese nicht vorhanden, setzt die Methode diese auf den Defaultwert 0. Das Zuweisen an self._cls spart weiter unten etwas Schreibarbeit.
Die Methode __pos__ (Zeile 11) liefert eine Kopie des Vektors. Die Verwendung von self._cls bzw. self.__class__ sorgt dafür, dass die Methode, angewandt auf abgeleitete Klassen, wiederum ein Objekt der abgeleiteten Klasse liefert.
Analog zu __pos__ liefert __neg__ (Zeile 14) eine negativ genommene Kopie des Vektors. Dazu wird der Vektor mit -1 multipliziert. Die Methode __add__ (Programmzeile 17) addiert zwei Vektoren. Die Implementation zeigt, dass sich die Summe jeweils aus den Summen der x- und der y-Komponenten ergibt.
Mit __sub__ (Zeile 20) zieht zwei Vektoren voneinander ab. Das geht mit wenig Code: Bei der Addition des ersten Vektor zählt die Methode einfach den Zweite mit umgekehrtem Vorzeichen hinzu. Die eigentliche Logik steckt in dem Methode __add__. Schreiben Sie weniger Code, schleichen sich auch weniger Fehler ein, und es fällt weniger Aufwand beim Testen an.
Die Multiplikationsmethode __mul__ (Zeilen 23 bis 32) kommt in zwei Varianten daher. Mathematisch ist es möglich, einen Vektor mit einem Skalar, also einer einfachen Zahl, zu multiplizieren. Zusätzlich gibt es das so genannte Skalarprodukt, das die Summe der Produkte der x- beziehungsweise y-Komponenten ist.
Um zu unterscheiden, ob die rechte Seite des Multiplikationsoperators ein Skalar oder ein Vektor ist, prüft die Try-Anweisung, ob der Wert ein Attribut x besitzt. Falls nicht, handelt es sich um einen Skalar und die Methode löst einen AttributeError aus. Float-Objekte haben kein Attribut x – Objekte vom Typ Vektor dagegen schon, weil die Klasse sie so definiert.
Die Fehlerbehandlung (Codezeilen 27 bis 29) multipliziert entsprechend die Komponenten des Vektors mit dem Skalar; ein neuer Vektor entsteht. Der Else-Zweig behandelt den Fall zweier Vektoren und gibt das Skalarprodukt zurück.
Die Programmzeilen 34 und 35 zeigen die Methode __rmul__. Diese läuft ab, wenn der andere Operand auf der linken Seite des Multiplikationszeichens steht. Bei der Addition beziehungsweise Subtraktion war das unnötig, weil links immer ein Vektor steht, und damit der richtige Aufruf von __add__ oder __sub__ gesichert ist. Bei der Multiplikation wäre dagegen links auch ein Skalar möglich.
Der Absolutbetrag eines Vektors errechnet sich mit Hilfe des Skalarprodukts des Vektors mit sich selbst (Zeile 37). Dabei hilft die Quadratwurzel-Funktion aus dem Modul math. Die restlichen Methoden der Klasse, __repr__ und __str__, kümmern sich um das Formatieren von Vektor-Objekten, beispielsweise bei der Ausgabe mit print.
Attributzugriff
Weisen Sie einer Variablen ein Objekt zu, verknüpft Python lediglich das Objekt auf der rechten Seite mit dem Namen auf der linken Seite (siehe erster Teil des Kurses). Steht auf der linken Seite dagegen eine Klasse mit Attribut (Klasseninstanz
.Attribut
), so besteht die Möglichkeit, den Zugriff abzufangen und besonders zu behandeln. Auf die gleiche Weise beeinflussen Sie lesende Zugriffe auf das Attribut und das Löschen (durch del).
Vor allem bei mehreren Attributen, die Sie ähnlich behandeln möchten, empfiehlt sich der Einsatz der Methoden __getattr__, __setattr__ und __delattr__, um den lesenden, schreibenden oder löschenden Attributzugriff zu steuern. Das Beispiel in Listing 6 demonstriert die Handhabung von Attributzugriffen anhand einfacher Stellvertreter-Objekte, die Attributzugriffe anzeigen und auf das eigentliche Objekt self._obj "umleiten".
class Proxy(object):
def __init__(self, obj):
self.__dict__['_obj'] = obj
def __getattr__(self, name):
print "Lese Attribut", name
return getattr(self._obj, name)
def __setattr__(self, name, wert):
print "Setze Attribut %s auf %s" % (name, wert)
setattr(self._obj, name, wert)
def __delattr__(self, name):
print "Loesche Attribut", name
delattr(self._obj, name)
def __call__(self):
return self._obj
Der Konstruktor in den Zeilen 2 und 3 setzt das Attribut self._obj direkt über das Objekt-Dictionary self.__dict__. Der Grund für die vordergründig komplizierte Technik folgt weiter unten bei der __setattr__-Methode. Hier sei nur gesagt, dass self.__dict__ den Namensraum des Objekts self als Dictionary zugänglich macht, genau so wie die eingebaute Funktion globals() das für den Namensraum eines Moduls tut.
Die Methode __getattr__ (Zeilen 5 bis 7) kommt immer dann zum Einsatz, wenn das gesuchte Attribut nicht im Suchpfad des Objektnamensraums vorhanden ist. Die Definition gibt mit einer Print-Anweisung aus, welches Attribut Sie angefordert haben, und holt das gewünschte Attribut aus dem enthaltenen Objekt self._obj.
Die Methode __setattr__ (Zeilen 9 bis 11) funktioniert ähnlich: Sie beschreibt mittels einer Print-Anweisung den Attributzugriff und leitet diesen an das Objekt self._obj weiter. Die Methode __setattr__ tritt bei jedem zu setzenden Attribut in Aktion, unabhängig davon, ob es bereits im Namensraum des Objekts vorhanden ist.
Lautete nun die Zuweisung im Konstruktor self._obj = obj, würde __setattr__ aufgerufen. In der Definition in Zeile 11 ruft self._obj implizit __getattr__ auf, worin in Zeile 7 wiederum __getattr__ aufgerufen würde. Dadurch käme es zu einer unendlichen Rekursion. Indem in Zeile 3 der Konstruktor den Namensraum des Objekts direkt verändert, den Aufruf von __setattr__ also umgeht, verhindert er die Rekursion.
Die __delattr__-Methode wiederum ist für das Löschen eines Attributs zuständig. Auch dieser Zugriff wirkt auf das enthaltene Objekt self._obj. Ein Aufruf von __call__ (ab Zeile 17) gibt das gekapselte Objekt zurück. Listing 7 zeigt ein Anwendungsbeispiel für die Proxy-Klasse im interaktiven Interpreter.
>>> import proxy >>> class C(object): … def methode(self, argument): … print argument … >>> x = C() >>> p = proxy.Proxy(x) >>> p.methode(7) Lese Attribut methode 7 >>> p.a = 1 Setze Attribut a auf 1 >>> print p.a Lese Attribut a 1 >>> print x.a # direkter Zugriff auf x 1 >>> print p().a # direkter Zugriff auf x 1 >>> del p.a Loesche Attribut a >>> print x.a Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: 'C' object has no attribute 'a'
Seit Python 2.2 gibt es außerdem so genannte Deskriptoren [7], die Zugriffe auf einzelne Attribute regeln. Deren Programmierung ist allerdings etwas komplizierter.
[1] StringIO-Modul: http://docs.python.org/lib/module-StringIO.html
[2] Spezielle Methoden: http://docs.python.org/ref/specialnames.html
[3] Rich Comparisons: siehe http://docs.python.org/ref/customization.html
[4] Container-Methoden: http://docs.python.org/ref/sequence-types.html
[5] shelve-Modul: http://docs.python.org/lib/module-shelve.html
[6] Numerische Typen: http://docs.python.org/ref/numeric-types.html
[7] Deskriptoren: http://users.rcn.com/python/download/Descriptor.htm



