I'm watching you

Code-Stücke können hier veröffentlicht werden.
Antworten
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
BlackJack

Das mit `None` ist IMHO unschön, da würde ich ein eigenes Sentinel-Objekt mit ``NOTHING = object()`` einführen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
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!?
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

BlackJack hat geschrieben:Dazu müsste ich mir Deinen Quelltext ja anschauen und verstehen. :-)
Aha, entlarvt :)

Stefan
Vingdoloras
User
Beiträge: 53
Registriert: Sonntag 2. Dezember 2007, 18:25

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
tordmor
User
Beiträge: 100
Registriert: Donnerstag 20. November 2008, 10:29
Wohnort: Stuttgart

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.
Vingdoloras
User
Beiträge: 53
Registriert: Sonntag 2. Dezember 2007, 18:25

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
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

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?).
Antworten