Objekte auf "Zuruf" erzeugen

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
Remline
User
Beiträge: 5
Registriert: Mittwoch 30. November 2011, 13:18

Hallo,

Ich habe da gerade ein Problem, das etwas kompliziert zu beschreiben, aber hoffentlich ganz einfach ist.

Ich habe es daher mal auf eines der beliebten Tier-Beispiele vereinfacht

Angenommen, ich habe 3 Klassen: Tier, Hund und Katze. Hund und Katze sind von Tier abgeleitet. Alle 3 Klassen besitzen eine Methode gibLaut() mit Rückgabe des entsprechenden Lautes. Die Klasse Tier gibt nur einen allgemeinen Laut zurück.
Ich möchte nun einen Aufruf folgender Art machen:

>>> bello = Tier("Hund")
>>> tom = Tier("Katze")
>>> bello.gibLaut()
'Wuff'
>>> tom.gibLaut()
'Miau'

Toll wäre:
>>> jerry = Tier("Maus")
>>> jerry.gibLaut()
'Das Tier gibt einen Laut'

Die Klasse Maus ist nicht definiert, daher ergibt ein Aufruf von gibLaut() den Rückgabewert aus der Klasse Tier. Sobald aber eine Klasse definiert ist soll der Rückgabewert der entsprechenden gibLaut() Methode aus der Klasse ausgegeben werden.
Die Klasse Tier sollte also feststellen können, ob eine übergebene Zeichenkette als Klasse erzeugt werden kann.

Abstrakt gesprochen möchte ich ein Objekt einer Klasse erzeugen. Die genaue Klasse ist mir aber erst zur Laufzeit bekannt.

Falls jemand das konkrete Problem hilft:
Ich möchte Werte aus Webseiten extrahieren. Dazu gibt es allgemeine Methoden (in der Klasse Tier) und spezielle Methoden, die auf die Website abgestimmt sind. dazu übergebe ich den Domainnamen("Hund","Katze",etc.) und habe Klassen, die auf diese Seiten spezialisiert sind.

Wie gehe ich da am besten ran?

Andy
deets

Klassischer Factory-Ansatz. Kann in Python mit allem moeglichen erstellt werden, eine Moeglichkeit nahe an deinem Beispiel waere eine ueberladene __new__-Methode. Andere koennten Meta-Klassen sein - oder einfach eine classmethod mit einem expliziten Mapping von URL auf Klasse.
Remline
User
Beiträge: 5
Registriert: Mittwoch 30. November 2011, 13:18

Ok, ich hatte schon das leichte Gefühl, das ich mich hier bei einem typischen Entwurfsmuster befinde. Die werde ich mir also heute Abend noch mal zu Gemüte führen.

Wenn ich damit nicht weiterkommen sollte komme ich noch mal auf das
deets hat geschrieben:.oder einfach eine classmethod mit einem expliziten Mapping von URL auf Klasse.
zurück. Das sagt mir nämlich gar nichts.

In der Praxis würde ich später gerne einfach Klassen(z.B Ente) in ein Verzechnis werfen, die ab dann genutzt werden ohne eine Zeile Code im restlichen Programm zu ändern.

Aber zunächst mal Danke für die schnelle Antwort.

Wird hier das formale schließen von Threads gepflegt? Falls ja mach ich den dann zu sobald ich einen kleinen Lösungscode hier verlinkt habe, damit auch andere was von der Lösung haben.
deets

"Einfach in ein Verzeichnis werfen" ist nicht so einfach. Dazu brauchst du dann noch ein paar Dinge mehr, irgend eine Art plugin-System. Das kann natuerlich auch file-basiert sein. Nur einfach so erfaehrt Python nix von neuen Dingen, da muss es schon explizite importe geben.

Aber das hat auch nichts mit deinem Problem zu tun. Natuerlich kann man mittels __new__ & einfacher Ableitung dafuer sorgen, dass Python automatisch Datenstrukturen aufbaut, die dann fuer die Factory zugrunde liegen.

Doch im Grunde macht das keinen echten Unterschied. Genauso gut kannst du in deinem Plugin verlangen, dass eine Implementation sich explizit registriert.

Hier ein Beispiel mit __new__. Aber wie gesagt - ohne import erfaehrt Python nix davon, ob du irgendwo auf deiner Festplatte eine Datei hast, in der von Base geerbt wird...

Code: Alles auswählen

class Base(object):


    URL = None


    def __new__(cls, url):

        def traverse(clazz):
            yield clazz
            for subclazz in clazz.__subclasses__():
                for c in traverse(subclazz):
                    yield c



        for c in traverse(Base):
            if c.URL and url.startswith(c.URL):
                break
        return object.__new__(c)



class A(Base):


    URL = "http://heise.de"


class B(Base):

    URL = "http://foo.bar"


class C(B):

    URL = "http://pille.palle"



print Base("http://heise.de")
print Base("http://foo.bar")
print Base("http://pille.palle")

Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Reicht zum Erstellen des passenden Klassenobjekts nicht einfach eine Funktion aus?

Code: Alles auswählen

ARTEN = {'hund': Hund, 'katze': Katze}

def gib_tier(art):
    Tierart = ARTEN.get(art.lower(), Tier)
    return Tierart()
Die Klassen `Hund` und `Katze` erben natürlich von `Tier`.
deets

@snafu

Wenn ich mich mal zitieren darf:
einfach eine classmethod mit einem expliziten Mapping von URL auf Klasse.
Macht am Ende keinen grossen Unterschied, was ich an der __new__-Methode mag ist das implizite - aber das sehen andere genau andersrum.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Oder wenn du wirklich über Verzeichnisinhalte (im Sinne einer Paketstruktur) gehen willst, würde mir dies einfallen:

Code: Alles auswählen

def get_tier(art):
    try:
        Tierart = __import__(art)
    except ImportError:
        Tierart = Tier
    return Tierart()
Müsste natürlich aus dem entsprechenden Verzeichnis heraus ausgeführt werden. Andernfalls wären noch ein paar Änderungen nötig.

//edit: Ups, ich bin gerade wohl etwas hektisch. Der o.g. Code funktioniert natürlich noch nicht. Ein Modul kann nicht einfach aufgerufen werden. :oops:

Du musst halt noch eine Klasse in das Modul einbauen, welche als Tierart-Exemplar genutzt werden kann. Übrigens habe ich bewusst einen großen Anfangsbuchstaben gewählt, um zu zeigen, das zunächst die Klasse selbst gebunden wird und erst bei der Rückgabe das Exemplar erzeugt wird. Ob mir das jeder in der Form gleichtun würde, kann ich schlecht sagen. Ist wahrscheinlich Geschmackssache.
Antworten