'Python - track (catch) variable changes in imported (instantiated) Class from another file (sensors readings)

I have a script, let's say "sensors.py" in which I have a Class "Meas", that reads measurements from several sensors. These comes from serial ports, program makes some calculations on them and changes the class "self.variable_a value" and another self variables also. The readings are in continuous mode, i.e. the program automatically waits for a message to come from the sensor to the serial port and read whole line (it's done via pyserial library). Some transmit readings at frequency of 10Hz, others 20Hz or 100Hz. This is a really big and messy class, therefore I put it in a separate file.

In my "main.py" script I import this "sensors" file and instantiate the "Meas" Class. Now I have a problem. How can I run immediately some "on_changed_var_a" function in "main" script, only when the "variable_a" in "Meas" object has changed - without consuming CPU power with while loop (constatly checking whether by any chance the variable has not changed) or waiting with time.sleep()? I need to get the sensors readings changes and then run another functions in "main" script in the most efficient way, as fast as possible. Thanks in advance!

EDIT: added example files

"sensors.py" file:

import random
import time
import threading


running = True


class Meas1:
    def __init__(self, xyz):
        self.xyz = xyz
        self.var_a = None
        thr1 = threading.Thread(target=self.readings, daemon=True)
        thr1.start()

    def readings(self):
        while running:
            # simulating 5Hz sensor readings:
            self.var_a = self.xyz * random.randint(1, 1000)
            print(self.var_a)
            time.sleep(0.2)

"main.py" file:

import time
import sensors
import threading


class MainClass:
    def __init__(self):
        print("started")
        self.sensor1 = sensors.Meas1(xyz=7)

        thr_ksr = threading.Thread(target=self.thr_keep_script_running, daemon=True)
        thr_ksr.start()

        # in this part I would like to run the on_changed_var_a function, immediately when var_a changes

        thr_ksr.join()

    def on_changed_var_a(self):
        print("var_a changed: ", self.sensor1.var_a)

    def thr_keep_script_running(self, t=10):
        time.sleep(t)
        sensors.running = False
        print("stopped, sleeping 1 sec")
        time.sleep(1)


mc = MainClass()


Solution 1:[1]

Not sure why this is tagged mutithreading. You need this function to be run on different thread?

To the problem. The easiest way would be to make Meas call function you will pass to it.

You could make variable_a a property and then in it's setter call the function you want. Function could be passed and assigned to self.call_on_a_change attr for example.

Edit: I don't think there is a way to make function execute on different thread (well, you could start a new one for that purpose, which sounds like a great solution to me).

Another problem with threads is that you give control to the system. It decides when and for how long which thread runs. So "as fast as possible" is constrained by that.

Nonetheless, you could create a threading.Lock and try to acquire it from main thread. Then in the reading thread upon change you could release the Lock and allow main thread to execute all call_on_a_change. Something like this:

import time
import threading

lock = threading.Lock()
# change to locked
lock.acquire()

a_change_callbacks = []

def on_changed_var_a(new_a):
    print(new_a)

def readings():
    a_change_callbacks.append(lambda: on_changed_var_a('first `a` change'))
    lock.release()
    time.sleep(5)
    a_change_callbacks.append(lambda: on_changed_var_a('second `a` change'))
    lock.release()
    time.sleep(5)
    a_change_callbacks.append(lambda: on_changed_var_a('third `a` change'))
    lock.release()


thr = threading.Thread(target=readings, daemon=True)
thr.start()

while True:
    lock.acquire()
    for callback in list(a_change_callbacks):
        callback()
        a_change_callbacks.remove(callback)

    if not thr.is_alive():
        break

It's not your class model, but I hope it's enough to show the idea :D

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1