如何触发价值变化的功能?

时间:2011-05-31 16:13:41

标签: python event-handling observer-pattern

我意识到这个问题与事件处理有关,我读过有关调度程序的Python事件处理程序,所以要么它没有回答我的问题,要么我完全错过了这些信息。

我希望只要值m()发生变化,就会触发对象A的方法v

例如(假设钱让人高兴):

global_wealth = 0

class Person()
    def __init__(self):
        self.wealth = 0
        global global_wealth
        # here is where attribute should be
        # bound to changes in 'global_wealth'
        self.happiness = bind_to(global_wealth, how_happy)

    def how_happy(self, global_wealth):
        return self.wealth / global_wealth

因此,每当更改global_wealth值时,类Person的所有实例都应相应更改其happiness值。

NB:我不得不编辑问题,因为第一个版本似乎暗示我需要getter和setter方法。对不起,感到困惑。

5 个答案:

答案 0 :(得分:66)

您需要使用Observer Pattern。 在以下代码中,一个人订阅接收来自全球财富实体的更新。当全球财富发生变化时,该实体会向其所有订户(观察员)发出变更警报。然后人自我更新。

我在这个例子中使用了属性,但它们不是必需的。一个小警告:属性仅适用于新样式类,因此类声明之后的(对象)是必须的。

class GlobalWealth(object):
    def __init__(self):
        self._global_wealth = 10.0
        self._observers = []

    @property
    def global_wealth(self):
        return self._global_wealth

    @global_wealth.setter
    def global_wealth(self, value):
        self._global_wealth = value
        for callback in self._observers:
            print('announcing change')
            callback(self._global_wealth)

    def bind_to(self, callback):
        print('bound')
        self._observers.append(callback)


class Person(object):
    def __init__(self, data):
        self.wealth = 1.0
        self.data = data
        self.data.bind_to(self.update_how_happy)
        self.happiness = self.wealth / self.data.global_wealth

    def update_how_happy(self, global_wealth):
        self.happiness = self.wealth / global_wealth


if __name__ == '__main__':
    data = GlobalWealth()
    p = Person(data)
    print(p.happiness)
    data.global_wealth = 1.0
    print(p.happiness)

答案 1 :(得分:12)

如果要在更改属性时执行代码,则可以使用属性。警惕改变属性时出现的大的副作用或显着的开销对使用您的API的人来说有点令人惊讶,所以在某些情况下您可能希望通过使用方法来避免它。

class A(object):

    def m(self, p_value):
         print p_value

    @property
    def p(self):
        return self._p 

    @p.setter
    def p(self, value)
        self._p = value
        self.m(value)

答案 2 :(得分:8)

你在寻找什么称为 (Functional) Reactive Programming对于Common Lisp,有单元格 - 请参阅Cells projectCells manifesto,对于python,有{{} 3}}

电子表格也使用相同的范例。对于跟踪多个相互关联的参数非常有用 - 例如在GUI编程中。

反应式编程类似于观察者模式,但有一个重要的区别:

  

与观察者模式的相似性然而,将数据流概念集成到编程语言中会使表达它们变得更容易,因此可以增加数据流图的粒度。例如,观察者模式通常描述整个对象/类之间的数据流,而面向对象的反应式编程可以针对对象/类的成员。

答案 3 :(得分:3)

您需要property

class MyClass(object):
    def __init__(self):
        self._x = None

    def x_setter(self, value):
        self._x = value

    def x_getter(self):
        return self._x

    x = property(x_getter, x_setter)

在这里,只要你想设置x MyClass().x = "foo",你就会使用x_getter方法 每当你想要检索x print MyClass().x时,你将使用x_setter方法。

答案 4 :(得分:1)

您可以尝试这样的事情:

class Variable:
    def __init__(self, v):
        self.v=v
        self.command=None
    def set(self, v):
        self.v=v
        if self.command!=None:
            self.command()
    def get(self):
        return self.v
    def trace(self, command):
        self.command=command

x=Variable(0)

def money():
    amount="{:.2f}".format(x.get())
    print("You have $"+amount+".")

x.trace(money)

x.set(5.55)
x.set(15.14)

如果需要参数,只需使用lambda函数即可。鉴于此(以及我最近更深入研究的公认答案),这里有一个更复杂的版本,包括评论,更多功能和示例:

class Variable: #This is a class for the variable you want to bind something to
    def __init__(self, v):
        self.v=v
        self.commands=[]
    def set(self, v): #Set the variable's value and call any bound functions
        self.v=v
        for x in self.commands:
            x()
    def get(self): #Get the variable's value
        return self.v
    def trace(self, *commands): #Bind one or more functions to the variable
        for x in commands:
            if x in self.commands:
                raise ValueError("You can’t add the same command object twice. If you need to, use another lambda function that calls the same function with the same parameters.")
        self.commands.extend(commands)
    def untrace(self, *commands): #Unbind one or more functions from the variable
        for x in commands:
            if x not in self.commands:
                raise ValueError(str(x)+" is not a traced command.")
        for x in commands:
            if x in self.commands:
                self.commands.remove(x)
    def clear_traces(self): #Removes all functions bound to the variable
        self.commands.clear()

x=Variable(0) #Make the variable, starting with a value of 0

def money(name): #Define the method to bind
    amount="{:.2f}".format(x.get())
    print(name+" has $"+amount+".")

sam=lambda : money("Sam") #We're making a new method to bind that calls the old one with the argument "Sam"
sally=lambda : money("Sally") #Another one (Sally and Sam will always have the same amount of money while they are both bound to the variable.)

#Bind them both to the value (not that this is practical, but we're doing both for demonstration)
x.trace(sam)
x.trace(sally)

#Set the value
x.set(5.55)
#Unbind the sam lambda function and set the value again
x.untrace(sam)
x.set(15.14)

"""
This prints the following:
> Sam has $5.55.
> Sally has $5.55.
> Sally has $15.14.
"""

替代

无论如何,您还可以使用Tkinter附带的内置功能,例如DoubleVar.trace()someWidget.wait_variable()

trace()方法允许您将方法绑定到StringVar,IntVar,FloatVar,DoubleVar,BooleanVar或此类变量。这是一个完整的Python 3.x示例:

from tkinter import *

tk=Tk()
tk.withdraw()

d=DoubleVar(master=tk, value=0)

def my_event_handler(*args):
    amount="{:.2f}".format(d.get())
    print("$"+amount)

d.trace(mode="w", callback=my_event_handler)

d.set(5.55)
d.set(15.12)

"""
This prints the following:
> You have $5.55.
> You have $15.12.
"""

您可能希望在程序结束时销毁Tk对象。但是,在我的例子中,似乎没有它就退出了。

wait_variable()是另一种替代方法,它会导致调用函数暂停而不会暂停GUI,直到您指定的变量发生更改。还有其他类似的方法。