简体   繁体   中英

How can a class run some function when any of its attributes are modified?

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

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()

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)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM