我希望能够告诉我的Python对象中的数据字段是否因某些事件而被修改。我以为我会实现这个 使用以下属性:
class C(object):
def get_scores(self):
print 'getter'
return self.__scores
def set_scores(self, value):
print 'setter'
self.__scores = value
self.__scoresWereChanged = True
scores = property(get_scores, set_scores, None, None)
def do_something(self):
if self.__scoresWereChanged:
self.__updateState() #will also set self.__scoresWereChanged to False
self.__do_the_job()
如果scores
是不可变的,这种方法很有效,但它scores
是一个列表,我更改了它中的单个元素,方法失败了:
In [79]: c.scores = arange(10)
setter
In [80]: print c.scores
getter
Out[80]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [81]: c.scores[3] = 999
getter #setter was not called
我可以通过
来解决这个问题In [84]: s = c.scores
getter
In [85]: s[3] = 2222
In [86]: c.scores = s
setter
但是,这当然不会像我想的那样优雅。
答案 0 :(得分:3)
我认为没有任何简单的解决方案。
如果您计划使用列表,如您的示例所示,那么您将需要子类list
,并且至少提供您自己的__setitem__
方法实现,以允许列表更改为受到监控。有关其他方法也被覆盖的完整示例,请查看urwid library代码。特别是,monitored_list.MonitoredList
实现了类似的东西。
除此之外,您可能还想了解在代码中定义属性的替代方法,这些方法在属性更新时提供通知事件,如traits或gobject(非常古老的教程,但仍然很高兴得到这个想法)。
答案 1 :(得分:2)
可以做到 - 我可以想到一种复杂的方式,其中每个对“受保护”属性的赋值都会为存储的对象创建一个动态包装类。该类将监视对象上任何“魔法”方法的访问(即__setitem __,__ setattr __,__ iadd __,__ isub ___,等等)。
动态创建这样一个类很有趣,但是对于每种情况都很有效,也很棘手。
我能想到的另一个选择是创建每个受保护属性的副本,并且在每次读取访问时,它将检查存储的属性是否仍然等于其副本。如果没有,它会将其标记为脏。
不是使用属性,而是设置描述符类(实现类似行为的“属性”)更清晰
from copy import deepcopy
class guarded_property(object):
# self.name attribute set here by the metaclass
def __set__(self, instance, value):
setattr(instance, "_" + self.name, value)
setattr(instance, "_" + self.name + "_copy", deepcopy(value))
setattr(instance, "_" + self.name + "_dirty", True)
def __get__(self, instance, owner):
return getattr (instance, "_" + self.name)
def clear(self, instance):
setattr(instance, "_" + self.name + "_dirty", False)
setattr(instance, "_" + self.name + "_copy", deepcopy(self.__get__(instance, None)))
def dirty(self, instance):
return getattr(instance, "_" + self.name + "_dirty") or not (getattr(instance, "_" + self.name + "_copy") == self.__get__(instance, None))
class Guarded(type):
def __new__(cls, name, bases, dict_):
for key, val in dict_.items():
if isinstance(val, guarded_property):
val.name = key
dict_[key + "_clear"] = (lambda v: lambda self: v.clear(self))(val)
dict_[key + "_dirty"] = (lambda v: property(lambda self: v.dirty(self)))(val)
return type.__new__(cls, name, bases, dict_)
if __name__ == "__main__":
class Example(object):
__metaclass__ = Guarded
a = guarded_property()
g = Example()
g.a = []
g.a_clear()
print g.a_dirty
g.a.append(5)
print g.a_dirty
g.a_clear()
print g.a_dirty