如何在值变化时触发函数?

发布于 2024-11-10 16:52:15 字数 747 浏览 1 评论 0 原文

我意识到这个问题与事件处理有关,并且我读过有关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 值。

注意:我必须编辑这个问题,因为第一个版本似乎表明我需要 getter 和 setter 方法。抱歉造成混乱。

I realise this question has to do with event-handling and i've read about Python event-handler a dispatchers, so either it did not answer my question or i completely missed out the information.

I want method m() of object A to be triggered whenever value v is changing:

For instance (assuming money makes happy):

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

So whenever the global_wealth value is changed, all instances of the class Person should change their happiness value accordingly.

NB: I had to edit the question since the first version seemed to suggest i needed getter and setter methods. Sorry for the confusion.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

神仙妹妹 2024-11-17 16:52:16

您需要使用观察者模式
在以下代码中,某人订阅接收来自全球财富实体的更新。当全球财富发生变化时,该实体会提醒所有订阅者(观察者)发生了变化。然后 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)

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)
莫言歌 2024-11-17 16:52:16

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

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)
木落 2024-11-17 16:52:16

您正在寻找的内容称为(函数式)响应式编程 Common Lisp 有 Cells – 请参阅 Cells 项目Cells 宣言 对于 python,有 网格库

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

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

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

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.

若水般的淡然安静女子 2024-11-17 16:52:16

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

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.

独﹏钓一江月 2024-11-17 16:52:16

您可以尝试这样的操作:

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

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.

智商已欠费 2024-11-17 16:52:16

晚了,但如果有人正在寻找一个通用的解决方案,他们可以使用无法修改的代码(例如,库中的对象),那么这个解决方案可能就是您正在寻找的:

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

像这样使用它:

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}

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}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文