16

每次属性发生更改时,我都使用属性来执行一些代码,如下所示:

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

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

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

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

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

>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
4

3 回答 3

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

例子:

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
于 2013-11-11T13:39:58.637 回答
3

这是一个版本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__(),我只是包含完整的代码以确保完整性和易于测试。

于 2013-11-08T23:01:31.323 回答
1

如果您想跟踪更改并且不想弄乱on_change()不同类中的方法,您可以functools.partial按照从此处开始的方式使用。

这样您就可以包装您的数据并完全隐藏它。获取/更改只能通过融合在该对象中的一些方法来实现。

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

注意:这里是为那些可能对其他方式感兴趣的人...

于 2013-11-08T23:32:14.733 回答