Question:
How to track the change of variable in Python?

Summary:

Tracking changes to variables in Python can be done in several ways, depending on what specifically you want to achieve. Here we are doing this from a descriptor. 


You can take advantage of the fact that a descriptor can know the name of the attribute to which it is bound via the __set_name__ method, and use that to maintain attributes on the target object:


Solution:

class TrackedValidatedInteger:

    def __init__(self, min_value=None, max_value=None):

        self.min_value = min_value

        self.max_value = max_value

        self.has_changed = False

        self.value = None


    def __set_name__(self, obj, name):

        self.name = name

        setattr(obj, f"{self.name}_changed", False)


    def __get__(self, obj, objecttype=None):

        return self.value


    def __set__(self, obj, value):

        if (self.min_value is not None and value < self.min_value) or (

            self.max_value is not None and value > self.max_value

        ):

            raise ValueError(

                f"{value} must be >= {self.min_value} and <= {self.max_value}"

            )

        self.value = value

        setattr(obj, f"{self.name}_changed", True)


Given the above implementation, we can create a class Example like this:

class Example:

    v1 = TrackedValidatedInteger()

    v2 = TrackedValidatedInteger()


And then observe the following behavior:

>>> e = Example()

>>> e.v1_changed

False

>>> e.v1 = 42

>>> e.v1_changed

True

>>> e.v2_changed

False

>>> e.v2 = 0

>>> e.v2_changed

True


Instead of maintaining a per-attribute <name>_changed variable, you could instead maintain a set of changed attributes:

class TrackedValidatedInteger:

    def __init__(self, min_value=None, max_value=None):

        self.min_value = min_value

        self.max_value = max_value

        self.has_changed = False

        self.value = None


    def __set_name__(self, obj, name):

        self.name = name

        if not hasattr(obj, "_changed_attributes"):

            setattr(obj, "_changed_attributes", set())


    def __get__(self, obj, objecttype=None):

        return self.value


    def __set__(self, obj, value):

        if (self.min_value is not None and value < self.min_value) or (

            self.max_value is not None and value > self.max_value

        ):

            raise ValueError(

                f"{value} must be >= {self.min_value} and <= {self.max_value}"

            )

        self.value = value

        obj._changed_attributes.add(self.name)




In that case, we get:

>>> e = Example()

>>> e._changed_attributes

set()

>>> e.v1 = 1

>>> e._changed_attributes

{'v1'}

>>> e.v2 = 1

>>> e._changed_attributes

{'v1', 'v2'}


Also, you can iterate over e._changed_attributes if you need to record all your changed values.


Explanation: 

In the above codes, TrackedVariable is a descriptor class that tracks changes to the variable it is assigned to. The __get__ method is called when accessing the variable, and the __set__ method is called when setting the variable. Within the __set__ method, you can perform actions to track changes, such as printing a message indicating the old and new values.


When you assign tracked_var to an instance of MyClass, it becomes an instance of the TrackedVariable descriptor. Whenever you set or get the value of tracked_var through an instance of MyClass, the corresponding __set__ or __get__ method of the descriptor is invoked, allowing you to track changes as needed.


Answered by: >larsks

Credit: >Stackoverflow


Suggested blogs:

>How .transform handle the splitted groups?

>Can I use VS Code's launch config to run a specific python file?

>Python: How to implement plain text to HTML converter?

>How to write specific dictionary list for each cycle of loop?

>Reading a shapefile from Azure Blob Storage and Azure Databricks

>How to replace a value by another in certain columns of a 3d Numpy array?

>How to fix "segmentation fault" issue when using PyOpenGL on Mac?


Submit
0 Answers