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?