Catching changes to a mutable attribute in python
每次属性发生更改时,我都使用属性来执行一些代码,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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" |
这很有效:
1 2 3 | >>> a = SomeClass(5) >>> a.attr = 10 Do some code here every time attr changes |
但是,如果我将可变对象存储在
1 2 3 4 5 6 7 | class Container(object): def __init__(self,data): self.data = data >>> b = SomeClass(Container(5)) >>> b.attr.data = 10 >>> |
假设
1 2 3 | >>> b = SomeClass(Container(5)) >>> b.attr.data = 10 Do some code here every time attr changes |
这里有一个版本的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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" |
例子:
1 2 3 4 5 6 | >>> 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 |
注意,对
这是另一个解决方案。某种代理类。您不需要修改任何类来监视它们中的属性更改,只需使用ovverriden
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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') |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 |
如果您想跟踪更改,并且不想在不同的类中处理与
这样,您就可以包装数据并完全隐藏它。获取/更改只能通过在该对象中融合的某些方法实现。
注意:python没有私有财产,按照惯例,我们都是成年人,并且违反规则。在您的情况下,API的用户不应该直接更改容器上的数据(在创建之后)。
注:这里是给那些可能对其他方式感兴趣的人……