2

Is there some general way to get a class to run a function when any of its attributes are modified? I wondered if some subprocess could be running to monitor changes to the class, but maybe there's a way to inherit from class and modify some on_change function that is a part of the Python class, a bit like how the default __repr__ method of a class can be modified. What would be some sensible approach here?

The actual application is not to just do a printout but to update entries in a database that correspond to data attributes of instantiated classes.

#!/usr/bin/env python class Event(object): def __init__(self): self.a = [10, 20, 30] self.b = 15 #def _on_attribute_change(self): # print(f'attribute \'{name_of_last_attribute_that_was_changed}\' changed') event = Event() event.a[1] = 25 # printout should happen here: attribute 'a' changed event.a.append(35) # printout should happen here: attribute 'a' changed event.c = 'test' # printout should happen here: attribute 'c' changed 
1

3 Answers 3

5

You can override the __setattr__ magic method.

class Foo: def on_change(self): print("changed") def __setattr__(self, name, value): self.__dict__[name] = value self.on_change() 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks muchly for your proposed solution there. I'm looking for something more general, like something that would cover data attributes experiencing an append operation or something, and the selected solution there appears to feature that functionality.
1

Recently I developed server side on python, I had to detect changes on lists/dictionary/whatever you want, the library that saved my life is traits. I highly recommend it. you can easly check what changed/removed/added to your attribute.

you can read more here.

specifically for your case, the notification chapter is the most relevant

here's a small snippet I just ran:

from traits.api import * class DataHandler(HasTraits): a = List([10, 20, 30]) b = Int(15) class Event(HasTraits): def __init__(self): super().__init__() self.data_handler = DataHandler() self.data_handler.on_trait_change(Event._anytrait_changed) @staticmethod def _anytrait_changed(obj, name, old, new): is_list = name.endswith('_items') if is_list: name = name[0:name.rindex('_items')] current_val = getattr(obj, name) if is_list: # new handles all the events(removed/changed/added to the list) if any(new.added): print("{} added to {} which is now {}".format(new.added, name, current_val)) if any(new.removed): print("{} removed from {} which is now {}".format(new.removed, name, current_val)) else: print('The {} trait changed from {} to {} '.format(name, old, (getattr(obj, name)))) e = Event() e.data_handler.b = 13 e.data_handler.a.append(15) e.data_handler.a.remove(15) e.data_handler.a.remove(20) 

outupts:

The b trait changed from 15 to 13 [15] added to a which is now [10, 20, 30, 15] [15] removed from a which is now [10, 20, 30] [20] removed from a which is now [10, 30] 

hope this helps.

Comments

1

You can override __setattr__.

class Event: def __init__(self): self.a = [10, 20, 30] self.b = 15 def __setattr__(self, attr, value): print(f'attribute {attr} changed') super().__setattr__(attr, value) 

However, this only detects assignment directly to the attribute. event.a[1] = 25 is a call to event.a.__setitem__(1, 25), so Event knows nothing about it; it is handled entirely by whatever value event.a resolves to.

If you don't want the assignments in __init__ to trigger the notifications, call super().__setattr__ directly to avoid invoking your override.

def __init__(self): super().__setattr__('a', [10, 20, 30]) super().__setattr(__('b', 15) 

1 Comment

Thanks very much for your code there. It covers nearly all I need to do, but it doesn't cover the need to do things like append to a data attribute. I'm investigating the approach suggested by the selected solution. Thanks again for your solution.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.