在python中捕获对可变属性的更改

Emm*_*mma 13 python

我每次对属性进行更改时都使用属性来执行某些代码,如下所示:

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"
Run Code Online (Sandbox Code Playgroud)

这非常有用:

>>> a = SomeClass(5)
>>> a.attr = 10
Do some code here every time attr changes
Run Code Online (Sandbox Code Playgroud)

但是如果我存储了一个可变对象attr,attr可以直接修改,绕过setter和我的更改检测代码:

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

>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
>>>
Run Code Online (Sandbox Code Playgroud)

让我们假设它attr只会用于存储类型的对象Container.是否有修改一个优雅的方式SomeClass和/或Container使SomeClass执行_on_change每当Container存储在对象attr被修改?换句话说,我希望我的输出是:

>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
Run Code Online (Sandbox Code Playgroud)

ndp*_*dpu 5

这是另一种解决方案。某种代理类。您不需要修改任何类来监视它们中的属性更改,只需ChangeTrigger使用 ovverriden_on_change函数将对象包装在派生类中:

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')
Run Code Online (Sandbox Code Playgroud)

例子:

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
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案比公认的解决方案要好得多,我建议使用这个解决方案。 (3认同)

And*_*ark 4

这是一个版本SomeClassContainer我认为它具有您正在寻找的行为。这里的想法是,对 的修改Container将调用与其关联的实例_on_change()的函数:SomeClass

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

    def __setattr__(self, name, value):
        if not hasattr(self, name) or getattr(self, name) != value:
            self.on_change()
        super(Container, self).__setattr__(name, value)

    def on_change(self):
        pass

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

    @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"
Run Code Online (Sandbox Code Playgroud)

例子:

>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
>>> b.attr.data = 10     # on_change() not called if the value isn't changing
>>> b.attr.data2 = 'foo' # new properties being add result in an on_change() call
Do some code here every time attr changes
Run Code Online (Sandbox Code Playgroud)

请注意,唯一的更改SomeClass是 中的第二行__init__(),为了完整性和易于测试,我只是包含了完整的代码。