17

I've searched around but didn't find anything like this. Let's say I have an army of threads, that keep reading and updating an integer variable x. I would like a callback for when x changes over a certain margin, let's say 500, to run the callback.

How can this be done without putting a heavy load on the system, like having a thread that has a while true and checks if the variable has changed? Performance is critical. But so are ethics.

In plain code would be something like this:

x = 10
def runMe():
    print('Its greater than 500!!') 

def whenToRun():
    return x >= 500

triggers.event(runMe, whenToRun)
4
  • 3
    Does x have to be a global variable, or it can be, say, an attribute of some object? Commented Aug 16, 2018 at 21:00
  • You can either "hide" the variable behind a setter which takes care of notifying the observers, or (if that is in no way doable), you can go for the suboptimal option of having a thread which constantly checks the value in the variable. Commented Aug 16, 2018 at 21:03
  • 1
    x will be an attribute of some object Commented Aug 16, 2018 at 21:03
  • @AndrasDeak Well, you can do. One way is through debugger hooks, which may be too slow. Alternatively, you can use your __setitem__-hooked container as a module dict, either in a hacky way, or with a nice import hook that gives all .py files your custom dict subclass. Maybe other ways as well. Commented Aug 16, 2018 at 21:03

2 Answers 2

33

You want to have a function (a "setter") which is called whenever the variable's value changes. A good way to do that is to define a @property. It will behave like a variable, but will have a getter function and a setter function.

Then, in the setter, call any callbacks you need, which will react to the change.

This should do the trick:

class ObjectHoldingTheValue:
    def __init__(self, initial_value=0):
        self._value = initial_value
        self._callbacks = []

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        old_value = self._value
        self._value = new_value
        self._notify_observers(old_value, new_value)

    def _notify_observers(self, old_value, new_value):
        for callback in self._callbacks:
            callback(old_value, new_value)

    def register_callback(self, callback):
        self._callbacks.append(callback)

Then, you can do:

def print_if_change_greater_than_500(old_value, new_value):
    if abs(old_value - new_value) > 500:
        print(f'The value changed by more than 500 (from {old_value} to {new_value})')

holder = ObjectHoldingTheValue()
holder.register_callback(print_if_change_greater_than_500)
holder.value = 7    # nothing is printed
holder.value = 70   # nothing is printed
holder.value = 700  # a message is printed
Sign up to request clarification or add additional context in comments.

2 Comments

this fixed a similar issue I had with a callback function and a framework... Thank you very much, this should be marked as a solution.
Awesome! Exactly the kind of pattern I was looking for that fits my use case, thanks!!!
7

If x is an attribute of some object, rather than a global variable, this is very simple: add a __setattr__ method:

class MyType:
    def __setattr__(self, name, value):
        if name == 'x':
            self.x_callback(value)
        super().__setattr__(name, value)

Obviously there are ways you can make this more flexible:

  • Add a way to register conditions dynamically instead of just always calling x_callback.
  • Make it a mixin class that can be attached to any other class letting you register(name, callback) whatever you want.
  • Combine the above, so you can register(name, condition, callback).
  • self.x_callback(oldval=self.x, newval=value) so the callback can see old and new values.
  • if self.x_callback(value): so the callback can accept or reject the change.
  • value = self.x_callback(value) so the callback can override the change.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.