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

时间:2013-11-08 22:38:16

标签: 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"

这很有效:

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

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

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

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

假设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

3 个答案:

答案 0 :(得分: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"

示例:

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

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

答案 1 :(得分:0)

如果您想跟踪更改并且不想在不同的类中使用on_change()方法,那么您可以使用functools.partial开头here显示的方式。

这样您就可以包装数据并完全隐藏它。只有通过在该对象内融合的某些方法才能获得/更改。

NB :python没有私有属性,按照惯例,我们都是成年人并且违反了规则。在您的情况下,您的api用户不应直接更改容器上的数据(创建后)。

NB :对于那些可能对其他方式感兴趣的人......

答案 2 :(得分:0)

这是另一种解决方案。某种代理类。您不需要修改任何类来监视它们中的属性更改,只需使用ovverriden ChangeTrigger函数在_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')

示例:

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