如何触发价值变化的函数?

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

我希望对象 A的方法 m()在值 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值。

注意: 由于第一个版本似乎建议我需要 getter 和 setter 方法,所以我不得不编辑这个问题。抱歉给你添麻烦了。

108078 次浏览

You need a 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)

Here, whenever you want to set x MyClass().x = "foo" you will use the x_getter method and whenever you want to retrieve x print MyClass().xyou will use the x_setter method.

You can use properties if you want to execute code when attributes are changed. Be wary that big side-effects or significant overhead occurring when an attribute is changed is a little bit surprising to anyone using your API, so in some cases you probably want to avoid it by using methods instead.

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)

You need to use the Observer Pattern. In the following code, a person subscribes to receive updates from the global wealth entity. When there is a change to global wealth, this entity then alerts all its subscribers (observers) that a change happened. Person then updates itself.

I make use of properties in this example, but they are not necessary. A small warning: properties work only on new style classes, so the (object) after the class declarations are mandatory for this to work.

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)

What are you looking for is called (Functional) Reactive Programming. For Common Lisp there is Cells – see Cells project and Cells manifesto and for python there is the Trellis library.

Spreadsheets also use the same paradigm. Very useful for keeping track of multiple interrelated parameters – like in GUI programming for example.

Reactive programming is similar to the Observer pattern, but with an important distinction:

Similarities with Observer pattern However, integrating the data flow concepts into the programming language would make it easier to express them, and could therefore increase the granularity of the data flow graph. For example, the observer pattern commonly describes data-flows between whole objects/classes, whereas object-oriented reactive programming could target the members of objects/classes.

You can try something like this:

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)

If you need arguments, just use a lambda function. In light of that (and the accepted answer I more recently examined more thoroughly), here's a more complex version with comments, more functionality and examples:

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.
"""

Alternative

Anyway, you can also use the built-in functionality that comes with Tkinter, with such as DoubleVar.trace() or someWidget.wait_variable().

The trace() method allows you to bind a method to a StringVar, IntVar, FloatVar, DoubleVar, BooleanVar or such variables. Here's a full working Python 3.x example:

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.
"""

You may want to destroy the Tk object at the end of the program. It seems to exit fine without it in my example, however.

wait_variable() is another alternative that causes the calling function to halt without halting your GUI until a variable you specified changes. There are other similar methods, too.

Late to the party, but if anyone is looking for a generalized solution that they can slap on code that can't be modified (say, an object from a library), this solution might be what you're looking for:

https://gist.github.com/samueldonovan1701/1d43f070a842a33b2e9556693b6d7e46

Use it like so:

class A():
x = 5
def f(a, b):
return a-b


a = A()


a = ObjectObserver.AttachTo(a)


a.onget("x", lambda (val, name, obj): print("a.x -> %s"%val))
a.onset("*", lambda (val, name, obj): print("a.x set to %s"%(name,val)))
a.oncall("*", lambda (args, kwargs, name, obj): print("a.%s called with %s & %s"%(name,args))


a.x = a.x - 10
--> a.x -> 5
--> a.x set to -5


a.f(1,2)
--> a.f called with (1,2) and {}


a.f(a=1, b=2)
--> a.f called with (,) and {'a':1, 'b':2}