Seite 1 von 1

I'm watching you

Verfasst: Samstag 8. November 2008, 09:23
von sma
Ergebnis meiner gestrigen Zugfahrt: Ein `Model` hält einen Wert und kann Beobachter informieren, wenn sich dieser Wert ändert:

Code: Alles auswählen

class Model(object):
    def __init__(self, value=None):
        self._value, self.observers = value, []
    
    def _getvalue(self):
        return self._value
    
    def _setvalue(self, value):
        if self._value != value:
            self._value = value
            for ob in self.observers:
                ob.update()
    
    value = property(_getvalue, _setvalue)
Ein `Filter` ist ein `Model`, dessen Wert von einem anderen Modell abhängt:

Code: Alles auswählen

class Filter(Model):
    def __init__(self, model, func):
        super(Filter, self).__init__()
        self._model, self._func = None, func
        self.model = model
    
    def _getmodel(self):
        return self._model
    
    def _setmodel(self, model):
        if self._model is not model:
            if self._model is not None:
                self._model.observers.remove(self)
            self._model = model
            if model is not None:
                model.observers.append(self)
            self.update()
    
    model = property(_getmodel, _setmodel)
    
    def _getfunc(self):
        return self._func
    
    def _setfunc(self, func):
        if self._func is not func:
            self._func = func
            self.update()
    
    func = property(_getfunc, _setfunc)
    
    def update(self):
        self.value = self._func(self._model.value) if self._model else None
    
    def release(self):
        self.model = None
Beispiel:

Code: Alles auswählen

m = Model(0)
f = Filter(m, lambda: x: x * x)
m.value = 4
print f.value
Würde man das Modell nicht mit 0 vorbelegen, sondern mit `None`, würde `*` ein Problem haben. Hier ist daher eine Funktion, die dafür sorgt, dass andere Funktionen niemals mit `None` als Argument aufgerufen werden:

Code: Alles auswählen

def no_none(func):
    def inner(*args):
        return func(*args) if all(args) else None
    return inner
Ein `Compose` ist ein `Model`, welches mehrere Modelle beobachtet und ansonsten wie ein Filter funktioniert:

Code: Alles auswählen

class Compose(Model):
    def __init__(self, models, func):
        super(Compose, self).__init__()
        self._models, self._func = models, func
        for m in self._models:
            m.observers.append(self)
        self.update()
    
    def _getfunc(self):
        return self._func

    def _setfunc(self, func):
        if self._func is not func:
            self._func = func
            self.update()

    func = property(_getfunc, _setfunc)
    
    def update(self):
        self.value = self._func(*tuple(m.value for m in self._models))
    
    def release(self):
        for m in self._models:
            m.observers.remove(self)
Nun kann ich meine eigene kleinen Tabellenkalkulation bauen:

Code: Alles auswählen

a1 = Model()
a2 = Model()
a3 = Compose((a1, a2), no_none(lambda x, y: x + y))
print a3.value
a1.value = 3
print a3.value
a2.value = 4
print a3.value
Stefan

Verfasst: Samstag 8. November 2008, 10:59
von BlackJack
Das mit `None` ist IMHO unschön, da würde ich ein eigenes Sentinel-Objekt mit ``NOTHING = object()`` einführen.

Verfasst: Sonntag 9. November 2008, 11:54
von sma
BlackJack hat geschrieben:Das mit `None` ist IMHO unschön, da würde ich ein eigenes Sentinel-Objekt mit ``NOTHING = object()`` einführen.
Ich verstehe nicht, wie das gegen ein "a1.value = None" helfen soll. Kannst du mal ein Beispiel geben?

Stefan

Verfasst: Sonntag 9. November 2008, 13:15
von BlackJack
Dazu müsste ich mir Deinen Quelltext ja anschauen und verstehen. :-)

Ich hatte bloss den Eindruck, dass `None` hier eine besondere Bedeutung hat und deshalb nicht als Wert verwendet werden kann.

Warum sollte man ein ``a1.value = None`` verhindern wollen, wenn `None` ein erlaubter Wert für die weiterverbarbeitenden Funktionen ist!?

Verfasst: Sonntag 9. November 2008, 15:00
von sma
BlackJack hat geschrieben:Dazu müsste ich mir Deinen Quelltext ja anschauen und verstehen. :-)
Aha, entlarvt :)

Stefan

Verfasst: Dienstag 25. November 2008, 12:46
von Vingdoloras
Kannst du mal eine Art beispielprogramm schreiben, damit man sieht, wie deine Tabellenkalkulation funktioniert?
Undi ch glaube, BlackJack meint, du sollst deinen Code kommentiern...

Gruß
Vingdoloras

Verfasst: Dienstag 25. November 2008, 13:53
von tordmor
Sieht doch ganz gut aus. Nur unschön ist, dass, da Filter selbst ein Model ist, leicht ein infinite loop entstehen kann. Und ich seh den Bedarf von Filter nicht ganz ein. Da kann man doch ein Compose mit nur einem Model nehmen. Und die Semantik einer Zuweisung an Filter.value ist unsauber.

Daher schlage ich vor, die Filter bzw. Compose Klassen nicht von Model abzuleiten, sondern diesen Klassen im Konstruktor zusätzlich das Model zu übergeben, das sie ändern sollen. Wenn man dann dem Model noch ein Attribut "formula" gibt, kann man letzteres bei setzen von value im Model löschen und dadurch "sauber" überschreiben.

Verfasst: Dienstag 25. November 2008, 15:32
von Vingdoloras
Hmm...
War eben noch in der Schule, hatte keine Zeit und nich genug Konzentration, um mir das Programm mal richtig durchzulesen. (Schon erstaunlich, in der Schule erscheint das wie n dicker Ballen Buchstaben, den man nicht versteht, und zu Hause is plötzlich alles so einfach... :D )

Code: Alles auswählen

m, lambda: x: x * x
Der erste Doppelpunkt muss weg

Was macht

Code: Alles auswählen

super(Filter, self).__init__()
(Im Konstruktor der Filter-Klasse)
Das soll nicht heißen, dass das falsch ist, sondern dass ich es nicht kenne :oops:
Ebenso

Code: Alles auswählen

model = property(_getmodel, _setmodel)
hmm was war noch...

Code: Alles auswählen

def no_none(func):
    def inner(*args):
        return func(*args) if all(args) else None
    return inner
löst bei mir n Syntax Error aus... (Welche Python-Version nutzt du?)

Und nochmal zum Konstruktor der Filter-Klasse

Code: Alles auswählen

self._model, self._func = None, func
self.model = model
erscheint mir etwas sinnlos, da du in den anderen Methoden der Klasse immer auf self._model zugreifst, was aber mit None belegt ist...

Bin gespannt auf die Antworten :wink:

Gruß
Vingdoloras

Verfasst: Dienstag 25. November 2008, 15:40
von DasIch

Verfasst: Dienstag 25. November 2008, 15:47
von Leonidas
Vingdoloras hat geschrieben:Was macht

Code: Alles auswählen

super(Filter, self).__init__()
(Im Konstruktor der Filter-Klasse)
Den Konstruktur der Elternklassen aufrufen.
Vingdoloras hat geschrieben:löst bei mir n Syntax Error aus... (Welche Python-Version nutzt du?)
Der Ternäre Operator braucht Python 2.5.

Verfasst: Dienstag 25. November 2008, 23:14
von sma
Vingdoloras, das Beispielprogramm sind die PRINT-Anweisungen ganz am Ende. Ich habe drei Zellen a1, a2 und a3. Man stelle sich vor, in der dritten steckt etwas, das Excel als `=A1+A2` darstellen würde.

tordmor, eine Endlosschleife sollte eigentlich dadurch vermieden werden, dass nur Änderungen propagiert werden - wenn man nicht gerade selbst Schleifen baut. Tatsächlich denke ich jetzt auch, dass ein Filter als Compose mit nur einem Model realisiert werden kann. Ist mir damals nicht aufgefallen.

Bei Filter hatte ich an so was gedacht, was z.B. Adobes Flex kann: Ein Label bekommt den Text "Noch {count} Minuten". Das Label-Widget selbst muss ein Modell überwachen, in diesem Fall ein Filter, der selbst ein Modell für "count", dass dann mit einer Funktion in den String verwandelt wird.

Stefan

Verfasst: Mittwoch 26. November 2008, 00:11
von str1442
super() sollte man imho nicht für derart einfache Aufrufe einsetzen.
Liest sich nur (etwas) komplizierter als die Klasse direkt aufzurufen. Die Begründung lautet meistens, das man sich so von der Basisklasse unabhängiger macht. Es macht eigentlich keinen Unterschied, ob man nun direkt die Basisklasse aufruft oder super nimmt, denn in super muss man auf jeden Fall den ausgeschriebenen Namen der eigenen Klasse angeben - sollte man im Zweifelsfall diesen ändern, hat man genausoviel Arbeit wie vorher. Und Superklassen sind in der Regel das Ergebnis eines mehr oder weniger guten Designs, weswegen sich dort tendenziell weniger ändert.

super(self.__class__, self) kann man auch nicht nehmen, da das abundzu (py 2.5) zu Endlosrekursion führt.

Afaik wurde super eingeführt um irgendwelche komplexeren Probleme mit Mehrfachableitungen von Superklassen zu beheben, wobei super auch dort kompliziert anzuwenden ist (super(Klassenname, self).__init__ soll bei mehr als einer Superklasse welche aufrufen?).