Programmierstyle: Decorators

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Hallo,

ich suche nach einer Lösung, die es mir erlaubt, ein bestimmtes Objekt auf verschiedene Weise zu repräsentieren. Und zwar geht es um Punktobjekte, die durch die Koordinaten x, y, z repräsentiert werden. Nun benötige ich je nach Fall eine andere Art der Darstellung.

In meiner Datenbank wäre die benötigte Repräsentation POINT(x,y,z). Als XML - Datei in der Art "<coordinate>x, y, z</coordinate>", wobei ich mehrere XML Dialekte bedienen muss, die immer ein wenig anders einen Punkt beschreiben.

Aus meiner PHP Zeit kenne ich noch das Decorator Pattern, das sowas ja relativ elegant ermöglicht. Den Schnökes mit den Interfaces wie in PHP kann ich mir ja sparen :D

Nur ist das auch in Python eine elegante Möglichkeit? Man könnte ja auch einfach je nach Repräsentationstyp eine Funktion schreiben und das Punktobjekt übergeben. Die Funktion konstruiert den String und gibt ihn zurück.

Bisher habe ich:

Code: Alles auswählen

class Point(object):

    def __init__(self, x, y, z=None):
        self.x = x
        self.y = y
        self.z = z

    def __repr__(self):
        return "%s %s %s" % (self.x, self.y, self.z)
und ein Dekorierer:

Code: Alles auswählen

class WKTPoint(object):

    def __init__(self, point):
        self.point = point

    def __repr__(self):
        if self.point.z == None:
            return "POINT(%s %s)" % (self.point.x, self.point.y)
        else:
            return "POINT(%s %s %s)" % (self.point.x, self.point.y, self.point.z)
Wahrscheinlich denke ich wieder zu kompliziert oder so, deshalb wäre ich für Tipps zur Herangehensweise dankbar ...

Gruß Frank
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Deine Idee mit den "Dekoratoren" verstehe ich in dem Zusammenhang nicht. Was spricht denn gegen eine einzige Punkt-Klasse, die verschiedene Methoden für das benötigte Ausgabeformat bereitstellt:

Code: Alles auswählen

class Point(object):

    def __init__(self, x, y, z=None):
        self.x, self.y, self.z = x, y, z

    def __repr__(self):
        return "%i %i" %(self.x, self.y) if self.z is None \
            else "%i %i %i" %(self.x, self.y, self.z)

    def xml_dialekt(self):
        return "<coordinate>%i, %i</coordinate>" %(self.x, self.y) if self.z is None \
            else "<coordinate>%i, %i, %i</coordinate>" %(self.x, self.y, self.z)


a = Point(3, 4, 5)
print a
print a.xml_dialekt()
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

numerix, Decorator Pattern != Dekoratoren. Das was man mit dem Decorator Pattern in anderen Sprachen so löst ist ausreichend trivial in Python dass man dafür eigentlich keinen eigenen Namen braucht.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Leonidas hat geschrieben:numerix, Decorator Pattern != Dekoratoren. Das was man mit dem Decorator Pattern in anderen Sprachen so löst ist ausreichend trivial in Python dass man dafür eigentlich keinen eigenen Namen braucht.
Danke! Da hatte ich nicht genau genug gelesen ... :oops:
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Hi,

danke soweit für die Antworten. Ich will eigentlich ungern meine Punktklasse mit der Darstellung belästigen. Es kann durchaus vorkommen, dass so ein Punkt auf mannigfaltige Weise dargestellt werden kann, so 7-10 fallen mir sofort ein. Es geht um die Konvertierung von Daten aus einem Format in mehrere andere. Es können auch immer noch weitere Formate dazukommen. Da fände ich so einen Rattenschwanz an verschiedenen Repräsentationsmethoden unelegant. Zumal die Punkte z.T. als Stützpunkte für Linienobjekte benutzt werden und dann wieder ganz anders repräsentiert werden müssten. Deshalb möchte ich eigentlich die Repräsentation ganz heraushaben.

Nett am Decorator Pattern ist halt, das egal, welchen Decorator man wählt, die Methode zur Darstellung heisst immer gleich.

Leonidas, könntest du das mit dem Decorator Pattern kurz ausführen, was so trivial ist? Ist mein Beispiel ein gutes? Oder geht das in Python ganz anders?


Nachtrag zum Decorator Pattern:
http://therning.org/magnus/archives/301
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

frabron hat geschrieben:danke soweit für die Antworten. Ich will eigentlich ungern meine Punktklasse mit der Darstellung belästigen.
Was meinst du mir Darstellung? Eine Klasse ist eine abstrakte Darstellung eines Punktes.
frabron hat geschrieben:Nett am Decorator Pattern ist halt, das egal, welchen Decorator man wählt, die Methode zur Darstellung heisst immer gleich.
Und nach welchem Kriterium wird ausgewählt was da für eine Methode aufgerufen wird?
frabron hat geschrieben:Leonidas, könntest du das mit dem Decorator Pattern kurz ausführen, was so trivial ist? Ist mein Beispiel ein gutes? Oder geht das in Python ganz anders?
Wikipedia hat geschrieben:In object-oriented programming, the decorator pattern is a design pattern that allows new/additional behaviour to be added to an existing class dynamically.
Sowas geht eben ziemlich trivial indem man einfach eine Methode an eine Klasse bindet, fertig.

Ich habe mir mal einen Decorator Pattern Blogeintrag angeschaut, speziell die Lösung von Trevor Squires. In Python sieht das recht unspektakulär aus:

Code: Alles auswählen

class Coffee(object):
    def cost(self):
        return 2

def whipped(fun):
    def cost():
        return fun() + 0.2
    return cost

def sprinkles(fun):
    def cost():
        return fun() + 0.3
    return cost

x = Coffee()
x.cost = sprinkles(x.cost)
x.cost = whipped(x.cost)
print x.cost()
Wobei die Python-Philosophie eher abgeneigt ist, seine Klassen auf diese Weise zu modifizieren.

Ich könnte mir aber auch vorstellen, dass man eine Klasse dynamisch zur Laufzeit erstellt und in dieser die Methoden überschreibt. Oder noch einige andere Tricks; hängt auch etwas ab, was du für ein Problem zu lösen versuchst.

Ich glaube am einfachsten wäre es wenn du so ein Beispiel machen würdest, wie du es letztendlich implementieren wolltest, damit man sieht zu was man dir konkret raten kann.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Hoi,

vielleicht ist so etwas ein Kompromiss?

Code: Alles auswählen

def func1(obj): return "<%f, %f, %f>" % (obj.x, obj.y, obj.z)
def func2(obj): return "<coordinate>%i, %i,%i</coordinate>" \
                                     % (obj.x, obj.y, obj.z)

repr_dict = {'dialect1': func1,
             'dialect2': func2}

class FooPoint(object):
    def __init__(self, x, y, z, dialect):
        self.x, self.y, self.z = x, y, z
        self.dialect = dialect

    def __repr__(self):
        return repr_dict[self.dialect](self)

x = FooPoint(1,2,3, 'dialect1')
print x
Na ja, sicher kein vollendetes Beispiel und auch nichts Neues, aber ungemein praktisch - und das dict und die Funktionen kann man auch in ein kleines Modul auslagern.

HTH
Christian
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Danke für die Antworten. Ihr benutzt beide Funktionen, um die Ausgabe des Objektes entsprechend zu modifizieren. Daran hatte ich auch schon gedacht, da fiel mir aber das Decorator Pattern ein ...

Das Beispiel von Christian finde ich eigentlich ganz fuchsig, nur finde ich die Festlegung der Darstellung bei der Erzeugung des Punktes nicht so gut, das würde ich wohl trennen. So könnte ich in einem ersten Schritt die Punkte und Linien einlesen und erzeugen und dann je nach Option das entsprechende Format auf die Punkte und Linien im weiterverarbeitenden Programmteil (Controller z.B. bei MVC) anwenden.
Und nach welchem Kriterium wird ausgewählt was da für eine Methode aufgerufen wird?
Mein nachgetragener Link zum Pattern erklärt eigentlich ganz gut, wie das Decorator Pattern funktioniert. Es basiert ja im Grunde darauf, dass die entsprechende Methode der Klasse im Decorator überschrieben wird.

Ein konkretes Beispiel:
In einer Datei stehen Punkte, die zusammen eine Linie (andere Datei) ergeben. Damit ich die Linien in ein anderes Format überführen kann, will ich sie in einem bestimmten Format in einer Datenbank speichern. Die Daten kann man mittels dem "Well Known Text" (WKT) Format im INSERT Befehl in die Tabelle schreiben. Das WKT definiert einen Punkt mittels folgender Darstellung: POINT(x y) in 2D oder POINT(x y z) in 3D. Linien werden mittels LINESTRING(x1 y1, x2 y2, x3 y3) beschrieben. Da aber früher oder später andere Formate gefragt sind (GML, KML), suche ich eine Methode, die Punkte und Linien entsprechend des Formates mit einer Darstellung zu "dekorieren".

Die Textdateien sehen ungefähr so aus:
Punkte:

Code: Alles auswählen

p1 4 5
p2 7 3
p3 9 1
usw.
Linie:

Code: Alles auswählen

line1 p1
line1 p2
line1 p3
usw.
Dazu habe ich mir eine Punktklasse geschrieben um die Punkte aufzunehmen und eine Linienklasse, die die Punktobjekte entgegennimmt. Jetzt möchte ich flexibel das Format für die Darstellung ändern (WKT, GML, KML, ...). Das Beispiel in meinem ersten Post erklärt die Sache am Punktobjekt eigentlich ganz gut, nur das es eben noch z.B. eine Klasse

Code: Alles auswählen

class KMLPoint(object):
    def __init__(self, point):
        self.point = point

    def __repr__(self):
        if self.point.z == None:
            return "<coordinate>%s, %s</coordinate>" % (self.point.x, self.point.y)
        else:
            return "<coordinate>%s, %s, %s</coordinate>" % (self.point.x, self.point.y, self.point
und drölfzig weitere gibt.

Es mangelt ja nicht an Möglichkeiten, das umzusetzen, nur habe ich mich gefragt, wie man das eben in Python machen würde. Im Prinzip benötige ich ja immer nur einen Decorator (WKT oder XML oder whatever) und nicht wie beim Kaffeebeispiel Milch, Zucker und Schokobrösel :)

In der Zeit, wo ich diesen Post jetzt getippt habe, hätte ich wahrscheinlich mein Programm auf irgendeine Art und Weise ans Laufen gebracht, aber man will ja auch seinen Stil verbessern, vor allem als Umsteiger. Und da ich gerade genug Zeit habe, mir solche Fragen zu stellen tue ich das auch und lerne aus der Diskussion hier :)
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Hallo fabron,
vielleicht hilft Dir ja so etwas:
(Jedenfalls ist es das, was mir noch dazu eingefallen ist.)

Code: Alles auswählen

class Point(object):

  def __init__(self, *coords):

    self.coords = coords
  

  def format(self, mode):

    return mode(self)


def xml_format(point):
    
  length = len(point.coords)
  content = (', '.join(['%s'] * length)) % point.coords 
  return '<point%s> %s </point>' % (length, content)


def lisp_format(point):
    
  length = len(point.coords)
  content = (' '.join(['%s'] * length)) % point.coords 
  return '(point%s %s)' % (length, content)


if __name__ == '__main__':

  p2 = Point(1, 2)
  p3 = Point(5, 6, 7)

  print lisp_format(p2)
  print xml_format(p3)
  # or:
  print p2.format(xml_format)
  print p3.format(lisp_format)
:wink:
yipyip
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

frabron hat geschrieben:... nur finde ich die Festlegung der Darstellung bei der Erzeugung des Punktes nicht so gut, das würde ich wohl trennen.
Das ist ja ohne Weiteres zu machen. Außerdem ist __repr__ wohl nicht, was Du wünscht, aber das muß ich ja nicht sagen. Umleiten in eine Datei wirst Du schon schaffen ;-).

Allerdings würde ich nicht so per se schlecht finden, dass eine Instanz des Objektes weiß in welchem Format sie ausgeschrieben werden soll. Das Argument kann ja einen Defaultwert haben und sich auch zur Laufzeit ändern. Ist halt Geschmackssache.

Gruß,
Christian
Benutzeravatar
Käptn Haddock
User
Beiträge: 169
Registriert: Freitag 24. März 2006, 14:27

Falls deine Datenbank PostgreSQL/PostGIS ist, dann gibt es da die entsprechenden Module z.B. GeoTypes(?), die könnten dir vieleicht einiges an Arbeit abnehmen.

Gruß Uwe
---------------------------------
have a lot of fun!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

frabron hat geschrieben:Mein nachgetragener Link zum Pattern erklärt eigentlich ganz gut, wie das Decorator Pattern funktioniert. Es basiert ja im Grunde darauf, dass die entsprechende Methode der Klasse im Decorator überschrieben wird.
Ja, ueber den Link bin ich auch gestolpert, aber ausser dass es eine unnoetige Klasse hat, sehe ich den Sinn nicht. Letztendlich ist es eine 1:1-Uebersetzung des java-Patterns und sowas ist oftmals unnoetig kompliziert.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Yipyips Idee finde ich auch ganz nett, der Repräsentationsmethode der Klasse einfach eine Funktion zur Darstellung zu übergeben, die das Objekt selber entgegennimmt. Falls ich das richtig verstehe, ist das ja so ähnlich wie Leonidas Vorschlag.
CM hat geschrieben: Außerdem ist __repr__ wohl nicht, was Du wünscht, aber das muß ich ja nicht sagen. Umleiten in eine Datei wirst Du schon schaffen .
Das __repr__ nicht die letztendliche Methode der Wahl ist, ist schon klar, aber zum Testen ist es halt nett, wenn man

Code: Alles auswählen

print Point
machen kann. Faulheit siegt :D. Den Rest bekomme ich schon hin (denke ich zumindest :D). Es ging mir hier bei diesem Thread auch eher um "best practice" ...

Hallo Uwe, vielen Dank für den Hinweis, die kannte ich tatsächlich nicht, und die Datenbank ist in der Tat eine Postgresql/Postgis Datenbank. War wohl nicht schwer zu erraten ;)
Der Übung halber werde ich für dieses Projekt aber die Typen selber stricken. Zumindest WKT ist ja nicht so schwer. Hast du Erfahrung, in wie weit diese Geotypes mit SQLAlchemy harmonieren? Erst die Tage war eine kurze Diskussion diesbezüglich auf der SQLAlchemy Mailinglist.
Leonidas hat geschrieben:Ja, ueber den Link bin ich auch gestolpert, aber ausser dass es eine unnoetige Klasse hat, sehe ich den Sinn nicht. Letztendlich ist es eine 1:1-Uebersetzung des java-Patterns und sowas ist oftmals unnoetig kompliziert.
Ja, die 1:1 Umsetzung habe ich so auch erkannt. Da steckt wohl die Java Philosophie dahiner, immer auf ein Interface zu programmieren, aber das entfällt ja mangels Möglichkeit in Python :D
Ich bin halt Geograf, der sich mit der Programmierung herumschlagen muss, ohne es gelernt zu haben. Da fehlt es immer wieder mal an Wissen und Praxis in vielen Dingen. Learning by Doing funktioniert ja oft ganz gut, aber manche Sachen erschliessen sich dann einem nicht immer sofort.

So halt auch mit diesem Pattern. Ich hätte es jetzt einfach 1 zu 1 umsetzen können, es hätte funktioniert und alles wäre gut. Aber dann hätte ich wohl nie die Alternativen erfahren und wie Andere an die Sache herangehen.

Auf jeden Fall möchte ich mich noch einmal für die Beiträge bedanken. Ich habe hier viel neues gelernt :)
Benutzeravatar
Käptn Haddock
User
Beiträge: 169
Registriert: Freitag 24. März 2006, 14:27

Hoi!
Ich denke kaum, das es Probleme mit SQLAlchemy gibt, ich hab da bisher allerdings auch nur mehr oder weniger ernsthaft mit rumgespielt. Du kannst dich zu dem Thema auch mal im Reich von Sean Gillies umsehen, der macht einige sehr interessante Python-Sachen im GIS-Bereich. Die PCL (http://trac.gispython.org/lab/wiki/GetTheCode) könnte auch hilfreich sein.

Grüssle Uwe
---------------------------------
have a lot of fun!
Antworten