Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching changes to a mutable attribute in python

Tags:

python

I am using properties to execute some code every time there is a change to an attribute, like this:

class SomeClass(object):
    def __init__(self,attr):
        self._attr = attr

    @property
    def attr(self):
        return self._attr

    @attr.setter
    def attr(self,value):
        if self._attr != value:
            self._on_change()
        self._attr = value

    def _on_change(self):
        print "Do some code here every time attr changes"

And this works great:

>>> a = SomeClass(5)
>>> a.attr = 10
Do some code here every time attr changes

But if I store a mutable object in attr instead, attr can be modified directly, bypassing the setter and my change-detection code:

class Container(object):
    def __init__(self,data):
        self.data = data

>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
>>>

Let's assume that attr is only ever going to be used to store an object of type Container. Is there an elegant way to modify SomeClass and/or Container to make SomeClass execute _on_change whenever the Container object stored in attr is modified? In other words, I want my output to be:

>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
like image 968
Emma Avatar asked Nov 08 '13 22:11

Emma


People also ask

What are mutable attributes?

Mutable attributes are modified by the system automatically. Here the administrator can be either a security officer or a user. In general, administrative actions are made by security officers.

Is float Python mutable?

Objects of built-in types like (int, float, bool, str, tuple, unicode) are immutable. Objects of built-in types like (list, set, dict) are mutable. Custom classes are generally mutable.

Are class attributes mutable Python?

A Python object is considered mutable if it can be changed in place without being reassigned. 00:15 There aren't many of these in Python. Objects of the collection types list , and set , and dictionaries are mutable. And objects of programmer-created classes are mutable as well.

Are classes immutable in Python?

User classes are considered mutable. Python doesn't have (absolutely) private attributes, so you can always change a class by reaching into the internals. For using your class as a key in a dict or storing them in a set , you can define a .


1 Answers

Here is another solution. Some kind of proxy class. You dont need to modify any classes to monitor attributes changes in them, only wrap object in ChangeTrigger derived class with ovverriden _on_change function:

class ChangeTrigger(object):
    def __getattr__(self, name):
        obj = getattr(self.instance, name)

        # KEY idea for catching contained class attributes changes:
        # recursively create ChangeTrigger derived class and wrap
        # object in it if getting attribute is class instance/object

        if hasattr(obj, '__dict__'):
            return self.__class__(obj)
        else:
            return obj 

    def __setattr__(self, name, value):
        if getattr(self.instance, name) != value:
            self._on_change(name, value)
        setattr(self.instance, name, value)

    def __init__(self, obj):
        object.__setattr__(self, 'instance', obj)

    def _on_change(self, name, value):
        raise NotImplementedError('Subclasses must implement this method')

Example:

class MyTrigger(ChangeTrigger):
    def _on_change(self, name, value):
        print "New value for attr %s: %s" % (name, value)

class Container(object):
    def __init__(self, data):
        self.data = data

class SomeClass(object):
    attr_class = 100
    def __init__(self, attr):
        self.attr = attr
        self.attr_instance = 5


>>> a = SomeClass(5)
>>> a = MyTrigger(a)
>>>
>>> a.attr = 10
New value for attr attr: 10
>>> 
>>> b = SomeClass(Container(5))
>>> b = MyTrigger(b)
>>> 
>>> b.attr.data = 10
New value for attr data: 10
>>> b.attr_class = 100        # old value = new value
>>> b.attr_instance = 100
New value for attr attr_instance: 100
>>> b.attr.data = 10          # old value = new value
>>> b.attr.data = 100
New value for attr data: 100
like image 145
ndpu Avatar answered Sep 21 '22 02:09

ndpu