Search code examples
pythondecoratorpython-decorators

Decorating a class to monitor attribute changes


I want to have classes that automatically send notifications to subscribers whenever one of their attributes change. So if I would write this code:

@ChangeMonitor
class ChangingClass(object):

    def __init__(self, x):
        self.x = x


changer = ChangingClass(5)
print("Going to change x.")
changer.x = 6
print("Going to not change x.")
changer.x = 6
print("End of program")

The output would be:

Going to change x
Old x = 5, new x = 6
Going to not change x.
End of program.

My question is how to implement the ChangeMonitor decorator class. In the above example I assume it will print a line indicating the changes of an attribute, but for useful purposes it could send notifications to subscribed objects.


Solution

  • You'd have to add a __setattr__() method:

    def ChangeMonitor(cls):
        _sentinel = object()
        old_setattr = getattr(cls, '__setattr__', None)
        def __setattr__(self, name, value):
            old = getattr(self, name, _sentinel)
            if old not is _sentinel and old != value:
                print "Old {0} = {1!r}, new {0} = {2!r}".format(name, old, value)
            if old_setattr:
                old_setattr(self, name, value)
            else:
                # Old-style class
                self.__dict__[name] = value
    
        cls.__setattr__ = __setattr__
    
        return cls
    

    This should handle existing __setattr__ hooks as well. The _sentinel is used to allow None as the old value too.

    Demo:

    >>> changer = ChangingClass(5)
    >>> changer.x = 6
    Old x = 5, new x = 6
    >>> changer.x = 6
    >>> # nothing printed
    ...
    >>> changer.x = None
    Old x = 6, new x = None
    >>> changer.x = 6
    Old x = None, new x = 6