Seite 1 von 1

Dynamischer Dispatch

Verfasst: Dienstag 23. März 2010, 18:38
von Ronnie
Ich bin immer noch mit den Meta-Programmierungs Grundlagen beschäftigt (wobei ich versuche Ruby-Beispiele in Python zu übertragen). Mich interessieren Verbesserungsvorschläge/Alternativen zu folgendem Snippet:

Code: Alles auswählen

>>> tags = ['a', 'html', 'div', 'head', 'body', 'h1', 'h2', 'h3', 'span']
>>> class HtmlMe (object):
		pass

>>> html = HtmlMe
>>> def tag_me(name, val):
		return "<" + name + ">" + val + "</" + name + ">"

>>> for tag in tags:
		setattr(html, tag, partial(tag_me, tag))

>>> html.html(html.body(html.h1("Hello World")))
'<html><body><h1>Hello World</h1></body></html>'
EDIT: Einrückung angepasst

Verfasst: Dienstag 23. März 2010, 19:00
von snafu
Machst du das zur Übung oder willst du einfach nur mit durch Funktionen dargestellten Elementen arbeiten? Falls letzteres der Fall ist, willst du vielleicht mal einen Blick in die Elemente für die E-Factory werfen, die lxml so mitliefert.

Verfasst: Dienstag 23. März 2010, 19:28
von Ronnie
snafu hat geschrieben:Machst du das zur Übung (...)
Ja, es geht um 's lernen!

Verfasst: Dienstag 23. März 2010, 21:13
von BlackJack
@Ronnie: Also ich finde das Beispiel schlecht. Wozu die Klasse? Die macht als Klasse überhaupt keinen Sinn sondern dient einfach nur als Namensraum.

Das würde ich in Python eher als Klasse mit Metaklasse lösen, oder mit dynamischen Attributzugriffen via `__getattr__()`. Dann könnte man zum Beispiel auch Unterklassen erstellen die mehr können, zum Beispiel verschiedene Versionen von HTML oder Mixins für SVG oder MathML.

Verfasst: Dienstag 23. März 2010, 21:22
von Ronnie
@BlackJack: erstmal vielen Dank für die Rückmeldung. Könntest du ein einfaches Beispiel liefern, oder alternativ eine Empfehlung wo ich mehr dazu lesen kann?

Verfasst: Mittwoch 24. März 2010, 10:52
von sma
Die Lösung, die ich zuerst in Python bauen würde, ist diese:

Code: Alles auswählen

class Element:
    def __init__(self, name):
        self.name = name
    
    def __call__(self, *args):
        return "<%s>%s</%s>" % (self.name, "".join(a for a in args), self.name)

class Html:
    def __getattr__(self, name):
        return Element(name)

h = Html()

print(h.html(h.body(h.h1("Hello"))))
Sie ist objektorientiert und fummelt nicht an den dicts einer Klasse herum, was ich als Monkey-Patching bezeichnen würde und nur im Notfall einsetzen würde. Sie ist aber recht lang.

Mit `partial` geht es in der `setattr`-Variante auch kürzer:

Code: Alles auswählen

def element(name, *args):
    return "<%s>%s</%s>" % (name, "".join(a for a in args), name)

class h: pass

for name in "html body h1".split():
    setattr(h, name, partial(element, name))

print(h.html(h.body(h.h1("Hello"))))
Ich nutze hier die Klasse als ein Modul für Arme. Ich mache mir nicht die Mühe, ein Exemplar davon zu bilden, sondern rufe statische Funktionen auf. Finde ich aber immer noch besser, als in einem Modul alles globale Funktionen anzulegen.

Doch ich sagte ja, `setattr` fühlt sich nicht gut an. Da baue ich mir die Klasse lieber gleich selbst:

Code: Alles auswählen

h = type("h", (), { n: partial(element, n) for n in "html body h1".split() })

print(h.html(h.body(h.h1("Hello"))))
Das letzte Beispiel erfordert Python 3.1. In Python 2.x ist eine dict-comprehension nur über Umwege möglich. Für solche Experimente ist aber ein aktuelles Python IMHO genau das richtige.

Stefan

Verfasst: Mittwoch 24. März 2010, 11:22
von BlackJack
@sma: "Nur über Umwege möglich" klingt irgendwie als wenn es komplizierter oder länger wäre. Ich würde das hier nicht als "Umweg" bezeichnen, sondern als ziemlich direkten Weg das in 2.x zu lösen:

Code: Alles auswählen

h = type("h", (), dict((n, partial(element, n)) for n in "html body h1".split()))

Verfasst: Mittwoch 24. März 2010, 16:31
von Ronnie
@sma, BlackJack: Vielen herzlichen Dank! Ich freue mich immer, wenn ich was dazu lernen kann! Jetzt muss es nur noch langsam ins Langzeitgedächtnis sickern.