Abstract Factory in Python ?

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
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Hallo,

ich überlege schon länger wie ich am besten eine Factory bauen kann,
die anhand von ein paar .ini zuordnungen klassen baut.

Leider ist mir nichts wirklich gutes eingefallen, da ich auf das evil eval verzichten möchte.


Ziel ist, das Klassen nach einer .ini mit bestimmten Methoden zusammengebaut werden.

Hat da jemand eine gute idee ?
BlackJack

So allgemein kann man nur sagen: Immer dran denken, dass in Python alles "first-class objects" sind.

Dann gibt's `getattr()` und `setattr()` und im Modul `new` sind vielleicht die `classobj()` und `instancemethod()` Funktionen nützlich für Dein Vorhaben.

Ansonsten zeig uns doch mal eine kleine Ini-Datei und wie die Schnittstelle und das Ergebnis Deiner Fabrik aussehen sollen.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Ok nehmen wir an ich möchte eine Auto-Objekt Factory.

In der .ini steht z.b.:

[BLUE_VW_PASSAT]
Manufacturer = VW
Model = PASSAT
blinker = vw_passatblinker
sound = vw_passat_sound


Dann sollte ein objekt herauskommen, was die methoden Manufacturer, Model, blinker, und sound hat.
Also dem objekt eine methode namens sound hinzufügen die eine vorher angelegte methode vw_passat_sound aufruft.

Wenn ich dann noch in die ini was anderes schreibe, z.b. eine definition für den VW Passat, aber wo z.b. ein pioneer soundsys verbaut wurde,
möchte ich dann eben sound = pioneer_sound hinschreiben, und die entstehende Klasse bzw. objekt hat dann als methode sound, die methode
pioneer_sound gebunden.


Hoffe ich konnte das halbwegs gut erklären.

Aber vielleicht ist das ja ein umständlicher weg sich Klassen zusammenzubauen ?

Achja, die klassen möchte ich auch cachen, das wenn das 2te mal ein blauer vw passat mit pioneer sound verlangt wird, nicht wieder der rechenaufwand für das erzeugen der klasse anfällt.

Auch soll in dem Fall, Auto an VW vererben, VW widerum an Passat.

Vielleicht würdet ihr das ja ganz anders machen ?


Als möglichkeit würde ich sehen, einen grossen funktionspool anzulegen,
und diese dann evtl einer klasse zuzuordnen je nach .ini eintrag.


:shock: :roll: :?:
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Ist es nicht einfacher den ganzen Kram direkt als Python-Klassen zu schreiben? Mit einem kleinen Compiler geht sowas durchaus, aber ich glaub nicht dass Du dadurch wirklich eine Ersparnis an Inhalt hast.

Wenn Du willst schreib ich einen kleinen Compiler dafür mal zusammen, aber überleg Dir echt ob Du nicht einfach die Klassen direkt anlegen willst.
--- Heiko.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Hallo Heiko,

danke, wäre durchaus hilfreich wenn du das tun würdest, vielen dank.



Warum ich nicht einfach eine .py file nehme und klassen um klassen vererbe, was rein von der programmierlogik einfacher ist, ist eigentlich nur :

Die Angst des Benutzers vorm Source. Ich möchte das jeder der das nutzen will mit ein paar erweiterungen in der .ini neue typen aus bestehenden "blöcken" zusammensetzen kann.

Es ist mir auch vom lerneffekt her sehr wichtig das mal zu versuchen.
(Da ich später python auch auf arbeit einsetzen will - und mich da gegen fiese java fans behaupten muss, bzw. klarmachen das python toll ist. ;-)

Ich hab noch drüber nachgedacht wie das gehen könnte, bin auf die Idee (dank euch) gekommen, das ich ein klassenobjekt (mit namen der section) erzeuge, und ihm dann mit setattr() methoden zufüge. Oder ist das die falsche idee / ansatz ?

Hatte auch mal kurz die idee von Blackjack mit dem marshal modul und "on the fly .pyc's" aufgegriffen ;)
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Es ist am einfachsten es wie folgendes zu lösen (behaupte ich :-)):

mkcls.py

Code: Alles auswählen

# -*- coding: iso-8859-15 -*-

from ConfigParser import ConfigParser

def listClasses(cfg):
    clslist = {}
    for sec in cfg.sections():
        optiondata = dict((k, cfg.get(sec,k).strip()) for k in
                          cfg.options(sec))
        derived = optiondata.pop("derived",None)
        clslist[sec] = (derived,optiondata)
    return clslist

def sortClasses(clslist):
    deps = {}
    for sec, (derived, _) in clslist.iteritems():
        deps.setdefault(derived,[]).append(sec)
    newlist = []
    stack = deps.pop(None,[])
    while stack:
        curcls = stack.pop(0)
        newlist.append((curcls,)+clslist[curcls])
        stack.extend(deps.pop(curcls,[]))
    if deps:
        raise ValueError("Not all dependencies could be resolved")
    return newlist

def makeClasses(clslist):
    rv = []
    for cls, derived, methods in clslist:
        rv.append("class %s(%s):\n" % (cls,derived or "object"))
        for method, name in methods.iteritems():
            rv.append("    from functions import %s as %s\n" % (name,method))
        if not methods:
            rv.append("    pass\n")
        rv.append("\n")
    return "".join(rv)

cfg = ConfigParser()
cfg.read("test.dat")
file("test.py","w").write(makeClasses(sortClasses(listClasses(cfg))))

import test

x = test.BMW_Z3()
x.spoiler()
x.radio()

y = test.BMW()
y.spoiler()
y.radio()

z = test.BMW_Z5()
z.spoiler()
z.radio()
z.extra()
Beispiel test.py, Ausgabe aus mkcls.py

Code: Alles auswählen

class PKW(object):
    from functions import default_no_spoiler as spoiler
    from functions import default_radio as radio

class BMW(PKW):
    from functions import bmw_radio as radio
    from functions import bmw_spoiler as spoiler

class BMW_Z3(BMW):
    from functions import telefunken_radio as radio
    from functions import bmw_spezial_spoiler as spoiler

class BMW_Z5(BMW_Z3):
    from functions import bmw_superspezial_spoiler as spoiler
    from functions import bmw_extrawunsch as extra
functions.py

Code: Alles auswählen

def default_no_spoiler(self):
    print "default_no_spoiler(",self,")"

def default_radio(self):
    print "default_no_radio(",self,")"

def bmw_spoiler(self):
    print "bmw_spoiler(",self,")"

def bmw_radio(self):
    print "bmw_radio(",self,")"

def bmw_spezial_spoiler(self):
    print "bmw_spezial_spoiler(",self,")"

def telefunken_radio(self):
    print "telefunken_radio(",self,")"

def bmw_superspezial_spoiler(self):
    print "bmw_superspezial_spoiler(",self,")"

def bmw_extrawunsch(self):
    print "bmw_extrawunsch(",self,")"
Konfigurationsdatei:

Code: Alles auswählen

[PKW]
spoiler = default_no_spoiler
radio = default_radio

[BMW]
derived = PKW
spoiler = bmw_spoiler
radio = bmw_radio

[BMW_Z3]
derived = BMW
spoiler = bmw_spezial_spoiler
radio = telefunken_radio

[BMW_Z5]
derived = BMW_Z3
spoiler = bmw_superspezial_spoiler
extra = bmw_extrawunsch
Beispielausgabe:

Code: Alles auswählen

modelnine@phoenix ~/test $ python mkcls.py
bmw_spezial_spoiler( <test.BMW_Z3 object at 0x2aaaaab4dd90> )
telefunken_radio( <test.BMW_Z3 object at 0x2aaaaab4dd90> )
bmw_spoiler( <test.BMW object at 0x2aaaab4ce9d0> )
bmw_radio( <test.BMW object at 0x2aaaab4ce9d0> )
bmw_superspezial_spoiler( <test.BMW_Z5 object at 0x2aaaab4cea90> )
telefunken_radio( <test.BMW_Z5 object at 0x2aaaab4cea90> )
bmw_extrawunsch( <test.BMW_Z5 object at 0x2aaaab4cea90> )
modelnine@phoenix ~/test $
Wenn Du nicht jedes mal den Code in eine externe Datei schreiben willst (oder aber um es direkt in ein .pyc zu schreiben) guck Dir das compile()-Modul an.
--- Heiko.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Wow, danke für die viele mühe.

Ich probier jetzt das mal aus ....


EDIT : schreiben werde ich die klassen vermutlich garnicht, will sie nur laden zur verwendung, werden also bei jedem start neu generiert, oder ich hebe die pyc's auf ,,, hmmm *überleg*
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Ein Versuch meinerseits, der auch zu funktionieren scheint :

Code: Alles auswählen

import new
tmp = new.classobj("myClass", (object,), globals())
o = tmp()

def method1(self):
    print "Iam the method appended to the class"

setattr(tmp, "method1_x", method1)

>>> print o1.method1_x()
Iam the method appended to the class
None
Seltsam finde ich allerdings das die methode explizit None zutückgibt - da scheint wohl pythonwin nicht zu erkennen das nicht angezeigt werden muss.

Da ich die Klassen eh in ein dict binden will ist das so denke ich ganz gut.

Aber wie mache ich so eine klasse im globalen namespace verfügbar ?

Also das sie dann entweder mit tmp() oder myClass() ganz normal verfügbar wird ?


EDIT: Das mit dem None scheint ein introspection (?) problem zu sein,
weil wenn ich das objekt instanziere nachdem ich die Klasse verändert habe gehts. siehe das ( direkt nach dem da oben)

Code: Alles auswählen

>>> o2 = tmp()
>>> o2.method1_x()
Iam the method appended to the class
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Code: Alles auswählen

>>> def nothing(): pass
...
>>> nothing()
>>> print nothing()
None
Klingelts? :wink:
TUFKAB – the user formerly known as blackbird
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Hi BlackBird, hast natürlich recht.

*Klingel*

Irgendwie hab ich das gestern absolut nicht mehr gecheckt ;-).

Und das ich bei dem einen ein print davor hatte und bei dem anderen nicht auch nicht ....
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Ich denk nicht dass es Dir irgendwas bringt wenn Du die Klassen so "on-the-fly" erstellst. Der Generator für die Klassen-Datei macht im Endeffekt genau das selbe, nur dass er einen Python-File draus erstellt.

Wenn Du jetzt nicht den File als "test.py" speicherst, sondern über compile.compile() ein Code-Objekt draus machst, kannst Du das ganze so behandeln wie auch Deine Methode. Vergiss auf jeden Fall unabhängig von allem nicht dass Du sowas wie listClasses() und sortClasses() auf jeden Fall auch implementieren mußt. sortClasses() ist extrem wichtig, weil Du die Reihenfolge in der die Klassen definiert werden anhand ihres Ableitungsbaums festlegen mußt.
--- Heiko.
Kompottkin
User
Beiträge: 21
Registriert: Sonntag 26. Februar 2006, 03:09
Wohnort: Penzberg
Kontaktdaten:

Mad-Marty möchte laut eigener Aussage »evil eval« vermeiden. Modulquelltext in eine Datei zu schreiben und die Datei dann zu laden, ist wohl ziemlich identisch zu eval, nur umständlicher :)

Ich persönlich finde eval allerdings nicht »evil«. Trotzdem halte ich es für sehr elegant, auszunutzen, daß Python eine dynamische Sprache ist.

Übrigens kann man Klassen auch auf ganz natürliche Weise on-the-fly erstellen:

Code: Alles auswählen

def neue_klasse(spezies_name):
    class NeueKlasse(object):
        spezies = spezies_name
        def __init__(self, name=spezies_name):
            self.name = name
            print "Neue Klasse. Name: %s" % self.name
    
    return NeueKlasse

Maus = neue_klasse("Maus")
Katze = neue_klasse("Katze")
print Maus
print Maus()
print Maus("Mickey")
print Katze
print Katze()
Nur ein Beispiel. Man beachte die Closure-Semantik. Python ist eine wundervolle Sprache :)
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

<klugscheiss-modus an>Also, erstens ist das alles andere als identisch zu eval, sondern eher zu exec<klugscheiss-modus aus> ;-)

und zweitens ist das eine einfache Art und Weise das erstellte zu cachen. Die Klassen (gerade wenn's viele sind) immer wieder neu zu erstellen ist auf die Dauer einfach nur Zeitverschwendung für's Programm. Deswegen halt so die halbautomatische Art und Weise das ganze zu bauen (mit evil-exec).

Ich schrieb am Ende meines Posts aber dass er im Endeffekt einfach makeClasses() umbauen muß, dann kann er damit auch dynamisch die Klassen erstellen. Zum Beispiel in dem er Code wie Deinen benutzt. sortClasses() und listClasses() sind aber unabhängig von allem trotzdem sehr sinnvoll.
--- Heiko.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Hi Heiko,

ja mit der Reihenfolge hast du nicht ganz unrecht. Aber ich werde einfach vorraussetzen das die Sections in der .ini in der richtigen reihenfolge sind.


Sicherlich könnte man jetzt das ganze in XML abbilden, aber ich könnte mir nichts besch... vorstellen als das in xml zu schreiben, viel zu umständlich imo.

Auserdem habe ich vor die Klassen zu pickle'n / shelve'n / marshal'n.


Noch eine Frage zum compile, wegen der Optimierung, bringt -o also die optimize was in nennenswertem umfang ?

Bzw wie hoch ist der Geschwindigkeitsgewinn allgemein von optimize ?
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Bzw wie hoch ist der Geschwindigkeitsgewinn allgemein von optimize ?
Wenn Du kein assert oder if __debug__ im Code benutzt ist der Geschwindigkeitsvorteil = 0.
--- Heiko.
Antworten