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

Posted

技术标签:

【中文标题】如何触发价值变化的功能?【英文标题】:How to trigger function on value change? 【发布时间】:2011-09-05 15:16:35 【问题描述】:

我意识到这个问题与事件处理有关,并且我已经阅读了有关 Python 事件处理程序和调度程序的信息,因此要么它没有回答我的问题,要么我完全错过了信息。

我希望在值v 发生变化时触发对象A 的方法m()

例如(假设金钱使人快乐):

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 方法。很抱歉造成混乱。

【问题讨论】:

您正在寻找的东西在 Python 中称为“属性”。 (懒得完整回答...) 太糟糕了......但这是第一个线索:) 【参考方案1】:

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

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

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)

【讨论】:

优秀:) 我真的很喜欢你的例子。我已经看到记录的观察者模式,但没有看到它是我需要的答案。不过有一件事:在您提供的代码的逻辑中,被观察者Global_Wealth 是主动的,而观察者Person 是被动的。然而,在现实生活中,观察者是活跃的:被观察者只是在改变自己,不会要求观察者改变。你明白我的意思吗?我只是为了在这里学习而质疑逻辑。但是观察者活跃的代码也能正常工作吗? 观察者是否必须“ping”被观​​察者以检查其值?那会更消耗资源,不是吗? @Benjamin:你现在谈论的是事件轮询,而不是事件处理(因此观察者模式可能不适用)。 @Benjamin 您需要第三个组件,即事件基础架构。在最简单的情况下,另一个全局对象,其中 'Person' 将自己注册为对 globalWealthChanged 事件感兴趣,并且 GlobalWealth 对象向它发出更改值的信号(“引发”或“触发”事件),只要发生更改. 请注意,这种实现可能会导致内存泄漏,因为GlobalWealth 持有对注册回调的strong 引用,从而使 Person 对象保持活动状态。解决方案是使用弱引用,如this answer 中所述。【参考方案2】:

如果你想在属性改变时执行代码,你可以使用属性。请注意,更改属性时发生的巨大副作用或显着开销对于使用您的 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)

【讨论】:

我不认为这是我想要的,除非我误解了你的回答:为了清楚起见,我编辑了我的问题。 已经有一段时间了,但我不明白这个答案如何不能解决 OP 的问题。对我来说,这是一个很好的答案。 如何在全局变量而不是类或函数上执行此操作?假设我们有 x=10 并且我重新分配 x=20 我需要该功能才能工作。 __setattribute 不起作用。 ***.com/questions/59675250/…【参考方案3】:

你在寻找什么叫做 (Functional) Reactive Programming。 对于 Common Lisp,有 Cells - 请参阅 Cells project 和 Cells manifesto,对于 python,有 Trellis library。

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

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

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

【讨论】:

另请参阅Conal Elliott’s blog,了解有关反应式编程范式的更多信息。 这很漂亮,谢谢 :) 实际上我想知道为什么这不是在 Python 中构建的。 不客气。可能是因为在 10 到 15 年前,将对象属性/属性与函数链接以自动更新它们会被认为是浪费宝贵的 CPU 周期。编程语言中流行的编程范式的变化往往非常缓慢(就人类世代而言),但 CPU 能力的增加呈指数级增长...... 硬件描述语言(例如 Verilog 和 VHDL)将敏感度列表作为语言的核心特征。当敏感列表中的某些内容发生更改时,将执行一个过程。运行时关注管理和分发事件(状态更改)。有关使用生成器进程执行相同操作的 pythonic HDL,请参见 MyHDL。【参考方案4】:

你需要一个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 方法。

【讨论】:

我不认为这是我想要的,除非我误解了你的回答:为了清楚起见,我编辑了我的问题。【参考方案5】:

你可以试试这样的:

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 函数。鉴于此(以及我最近更彻底地研究过的公认答案),这里有一个更复杂的版本,包含 cmets、更多功能和示例:

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,直到您指定的变量发生更改。还有其他类似的方法。

【讨论】:

我可以在全局变量上添加这个变量吗?你如何跟踪全局变量的值变化?我不是在谈论函数或类对象或属性?假设我们有一个全局变量x=10;。我想要的是当一个新值被分配给 x 时,我想要调用观察者函数。 setattribute 在对象类上执行此操作不起作用。 ***.com/questions/59675250/… @Gary 不。一切都是 Python 中的对象(数字、函数、字符串,一切,除了可能的运算符,但你也可以修改它们的作用)。但是,您可以使用作为对象的全局变量。如果您需要更高的透明度(因此您不会觉得它是一个对象),请定义更多以双下划线开头和结尾的方法。您可以像跟踪任何其他变量一样跟踪全局变量值的变化。这只是一个范围。 您不能为普通变量重载赋值。但是,您可以为对象的属性/属性重载赋值。看到这个问题:***.com/questions/11024646/…。所以,你可以做 x.x=5 并让它调用一个函数,但不仅仅是 x=5。变量本身实际上并不是对象(AFAIK)。它们只是名字。 你可以用 __iadd__ (和其他类似的)重载 += 和其他类似的。

以上是关于如何触发价值变化的功能?的主要内容,如果未能解决你的问题,请参考以下文章

数商云S2B2C商城积分商城功能如何实现家用电器企业营销价值最大化?

数商云S2B2C商城积分商城功能如何实现家用电器企业营销价值最大化?

BASH:百分比变化 - 如何计算?如何在没有bc的情况下获得绝对价值?

为啥核心数据不会触发我的价值转换器?

通过链接保持价值变化

打开菜单时,JavaFX 焦点在 TextField 上的变化