记录对python对象的数据成员中的元素的访问

时间:2011-12-06 09:45:33

标签: python encapsulation

我希望能够告诉我的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

但是,这当然不会像我想的那样优雅。

2 个答案:

答案 0 :(得分:3)

我认为没有任何简单的解决方案。

如果您计划使用列表,如您的示例所示,那么您将需要子类list,并且至少提供您自己的__setitem__方法实现,以允许列表更改为受到监控。有关其他方法也被覆盖的完整示例,请查看urwid library代码。特别是,monitored_list.MonitoredList实现了类似的东西。

除此之外,您可能还想了解在代码中定义属性的替代方法,这些方法在属性更新时提供通知事件,如traitsgobject(非常古老的教程,但仍然很高兴得到这个想法)。

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