Python callback when variable changes
Published: 2024-02-23 04:03:13

In [Python](https://pythonkb.com/), you can achieve the functionality of executing a callback when a variable changes using various techniques. One common method is to use properties or setter methods along with the observer pattern. Below, I'll provide five examples demonstrating different ways to implement this functionality, along with explanations and sample outputs.

### Example 1: Using a Property

```python
class VariableWatcher:
    def __init__(self):
        self._value = None

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

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

    def on_change(self, new_value):
        print(f"Variable value changed to: {new_value}")


# Example usage
watcher = VariableWatcher()
watcher.value = 10
watcher.value = 20
```

**Explanation:**
- We define a class `VariableWatcher` with a private attribute `_value` to store the variable value.
- We define a `value` property with a setter method. When the `value` property is set, it triggers the setter method, which updates the `_value` attribute and calls the `on_change` method.
- The `on_change` method is where you would put your callback logic. In this example, it simply prints the new value.
- When you run this code, you'll see that the `on_change` method gets called each time the `value` property is set.

**Output:**
```
Variable value changed to: 10
Variable value changed to: 20
```

### Example 2: Using a custom setter method

```python
class VariableWatcher:
    def __init__(self):
        self._value = None

    def set_value(self, new_value):
        self._value = new_value
        self.on_change(new_value)

    def on_change(self, new_value):
        print(f"Variable value changed to: {new_value}")


# Example usage
watcher = VariableWatcher()
watcher.set_value(10)
watcher.set_value(20)
```

**Explanation:**
- This example is similar to the first one, but instead of using a property, we have a custom setter method `set_value`.
- Whenever `set_value` is called, it updates the `_value` attribute and calls the `on_change` method.
- The `on_change` method remains the same as in the previous example.

**Output:** (Same as Example 1)

### Example 3: Using custom observer pattern

```python
class VariableWatcher:
    def __init__(self):
        self._value = None
        self.observers = []

    def set_value(self, new_value):
        self._value = new_value
        self.notify(new_value)

    def add_observer(self, observer):
        self.observers.append(observer)

    def notify(self, new_value):
        for observer in self.observers:
            observer(new_value)


# Example usage
def callback(new_value):
    print(f"Variable value changed to: {new_value}")

watcher = VariableWatcher()
watcher.add_observer(callback)
watcher.set_value(10)
watcher.set_value(20)
```

**Explanation:**
- This example implements a simple observer pattern.
- The `VariableWatcher` class maintains a list of observers (callbacks).
- The `add_observer` method allows adding new observers to the list.
- When the `set_value` method is called, it updates the `_value` attribute and notifies all observers by calling them with the new value.
- In this example, we define a callback function `callback` and add it as an observer.

**Output:** (Same as Example 1)

### Example 4: Using `property` decorator with a function

```python
class VariableWatcher:
    def __init__(self):
        self._value = None

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

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

    def on_change(self, new_value):
        print(f"Variable value changed to: {new_value}")


# Example usage
watcher = VariableWatcher()

def callback(new_value):
    print(f"Callback: Variable value changed to: {new_value}")

watcher.value = 10
watcher.on_change = callback  # Replace the callback
watcher.value = 20
```

**Explanation:**
- This example is similar to Example 1, but we're demonstrating that you can change the `on_change` behavior dynamically.
- After creating an instance of `VariableWatcher`, we define a `callback` function.
- We then replace the `on_change` method with this callback function dynamically by assigning it to the `on_change` attribute of the instance.
- When `value` is updated, it triggers the `on_change` method, which now calls our callback function instead.

**Output:**
```
Variable value changed to: 10
Callback: Variable value changed to: 20
```

### Example 5: Using a library (watchdog)

```python
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class MyHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.src_path == "./myfile.txt":
            print("File modified! Callback triggered.")

# Example usage
observer = Observer()
observer.schedule(MyHandler(), path='.')
observer.start()

# Simulate file modification
with open("myfile.txt", "w") as f:
    f.write("Hello, world!")

observer.stop()
observer.join()
```

**Explanation:**
- In this example, we use the `watchdog` library to monitor file system events.
- We define a custom event handler `MyHandler` which inherits from `FileSystemEventHandler`.
- We override the `on_modified` method to specify what should happen when a file is modified. In this case, it prints a message.
- We create an `Observer` instance, attach our event handler to it, and start observing the current directory.
- Then, we simulate a file modification by creating a file `"myfile.txt"` and writing some content to it.
- Finally, we stop the observer and join it.

**Output:**
```
File modified! Callback triggered.
```

These examples showcase different ways to implement callbacks when a variable changes in Python, ranging from basic property setters to more advanced techniques using libraries like `watchdog`.