如何触发价值变化的功能?
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商城积分商城功能如何实现家用电器企业营销价值最大化?