90

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.

neydroydrec
  • 6,973
  • 9
  • 57
  • 89

6 Answers6

127

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)
Thiago Chaves
  • 9,218
  • 5
  • 28
  • 25
  • 4
    Excellent :) I really like your example. I had seen the observer pattern documented but hadn't seen how it was the answer i needed. One thing though: in the logic of the code you presented, it is `Global_Wealth` the observed, which is active, whilst `Person`, the observer, is passive. In real life situation however, it would be the observer that is active: the observed is merely changing itself, and would not request the observer to change. Do you see my point? I'm just questioning the logic for the sake of learning here. But could an observer-active code be functioning too? – neydroydrec May 31 '11 at 19:38
  • Would the observer then have to 'ping' the observed to check its value? That would be more resource consuming then, wouldn't it? – neydroydrec May 31 '11 at 19:39
  • @Benjamin: What you're now talking about is event-polling, not event-handling (so the Observer Pattern likely doesn't apply). – martineau May 31 '11 at 20:58
  • +1 Nice implementation, although naming a class `GlobalWealth` is a little questionable -- would really depend on how instances of it are used. Maybe it should be made into a Singleton... – martineau May 31 '11 at 21:12
  • 1
    @Benjamin You would need a third component, an event infrastructure. In the simplest case another global object, where the 'Person's register themselves as being interested in the ``globalWealthChanged`` event, and the GlobalWealth object signaling the changed value to it ('raising' or 'firing' the event), whenever a change occurs. – ThomasH May 31 '11 at 21:22
  • @Benjamin In the classic observer pattern, like shown here, the observed notifies the observers that something happened and the observers do whatever they want with this information. The observed entity is active in the sense that it is the one that starts the process. In this simples example though, you could make happiness a calculated attribute, i.e., you never store its value in Person and always get the freshest global_wealth and recalculate it every time the Person's happiness is needed. – Thiago Chaves May 31 '11 at 21:57
  • @Martineau: thanks, i'll look into that. @ThomasH: if i understand you well—let me know otherwise—you are suggesting a middle-man, which informs subscribed objects of the status of other objects they are interested in. – neydroydrec May 31 '11 at 22:00
  • @martineau Making it a Singleton is a great idea. I tried to change the example code as little as possible and that is the result I got. – Thiago Chaves May 31 '11 at 22:00
  • @Thiago Chaves: Sorry, but no, IMHO that wasn't an improvement -- and not what I meant. Your class is really just what could be called an ObservableWealth. Instances of it could be either global or local. A Singleton is a class that only allows one instance to be created. There's many ways to do this, one simple approach is to write something like `name = name()` to rebind the class identifier to a single instance of the class (and make another hard to create). With something like that the code in your `Person` class would then need to explicitly refer to this global variable. – martineau Jun 01 '11 at 01:31
  • @martineau: Ok. That's an interesting way of creating a Singleton in Python, and I was not aware of it. Thanks. – Thiago Chaves Jun 01 '11 at 12:36
  • 3
    Please mind that this implementation could lead to memory leaks, because `GlobalWealth` holds **strong** references to the registered callbacks, keeping the Person objects alive. The solution is to use weak references as explained in [this answer](https://stackoverflow.com/a/14922132/5040035). – plamut May 14 '18 at 15:37
23

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)
jason m
  • 6,519
  • 20
  • 69
  • 122
Rosh Oxymoron
  • 20,355
  • 6
  • 41
  • 43
  • 2
    i don't think this is what i want, unless i misunderstand your answer: i edited my question for the sake of clarity. – neydroydrec May 31 '11 at 18:29
  • 5
    It's been some time, but I don't see how this answer doesn't solve the OP's problem. To me, it's a good answer. – pcko1 Mar 07 '19 at 08:09
  • 1
    How do I do this on a global variable and not a class or a function? Say we have `x=10` and I reassign x=20 I need the function to work. __setattribute doesn't work. https://stackoverflow.com/questions/59675250/capture-change-of-global-variable-value-in-python/59678279?noredirect=1#comment105518224_59678279 – Gary Jan 12 '20 at 11:02
10

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.

peterhil
  • 1,536
  • 1
  • 11
  • 18
  • Also see [Conal Elliott’s blog](http://conal.net/) for much more information about the Reactive Programming paradigm. – peterhil Jun 02 '11 at 23:45
  • This is beautiful, thanks :) Actually i am wondering as to why this is not built in Python in the first place. – neydroydrec Jun 03 '11 at 00:06
  • You’re welcome. Maybe it is because 10–15 years ago, it would’ve been considered a waste of precious CPU cycles to link object properties/attributes with functions to update them automatically. Changes in what are the popular programming paradigms in programming languages tend to happen really slowly (in terms of human generations), and yet the increase in CPU power raises exponentially... – peterhil Jun 03 '11 at 00:45
  • Hardware Description Languages (E.G. Verilog & VHDL) have sensitivity lists as a central feature of the language. A procedure is executed when something on its sensitivity list changes. The runtime is concerned with managing and distributing events (changes to states). See MyHDL for a pythonic HDL using generator processes to do the same thing. – David Johnston Jul 04 '16 at 20:03
4

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.

Cédric Julien
  • 78,516
  • 15
  • 127
  • 132
  • 1
    i don't think this is what i want, unless i misunderstand your answer: i edited my question for the sake of clarity. – neydroydrec May 31 '11 at 18:29
2

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.

Brōtsyorfuzthrāx
  • 4,387
  • 4
  • 34
  • 56
  • Can I add this on variable on globals variables? How do you track global variable's value change? I am not talking about a function or a class object or attributes? Let's say we have a global variable `x=10;`. What I want is when an new value is assigned to x then I want the observer function to invoke. __setattribute__ on the object class to do this doesnt work. – Gary Jan 12 '20 at 10:55
  • https://stackoverflow.com/questions/59675250/capture-change-of-global-variable-value-in-python/59678279?noredirect=1#comment105518224_59678279 – Gary Jan 12 '20 at 10:59
  • @Gary No. Everything is an object in Python (numbers, functions, strings, everything, except maybe operators, but you can modify what they do, too). You can use global variables that are objects, however. If you need more transparency (so you don't feel like it's an object), define more of those methods that begin and end with double underscores. You track a global variable value change the same as any other variable. It's just a scope. – Brōtsyorfuzthrāx Jan 16 '20 at 14:03
  • You can't overload assignment for a plain variable. However, you can overload assignment for attributes/properties of an object. See this question: https://stackoverflow.com/questions/11024646/is-it-possible-to-overload-python-assignment. So, you could do x.x=5 and have it call a function, but not just x=5. The variables themselves aren't actually objects (AFAIK). They're just names. – Brōtsyorfuzthrāx Jan 16 '20 at 14:48
  • You can overload += and other such, though, with `__iadd__` (and other such). – Brōtsyorfuzthrāx Jan 16 '20 at 14:56
  • See Cédric Julien's answer for how to do properties (such as x.x=5). There is a simpler syntax for it, though (with @property). – Brōtsyorfuzthrāx Jan 16 '20 at 15:00
0

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}

Sam
  • 11
  • 2