Регистрация доступа к элементам в элементах данных объектов Python - PullRequest
4 голосов
/ 06 декабря 2011

Я хочу иметь возможность сообщить, было ли изменено поле данных в моем объекте 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 ]

3 голосов
/ 06 декабря 2011

Я бы сказал, что для этого нет простого решения.

Если вы планируете использовать списки, как в вашем примере, то вам нужно будет создать подкласс list и, по крайней мере, предоставить собственную реализацию метода __setitem__, чтобы можно было отслеживать изменения списка. Для полного примера, в котором другие методы также перезаписываются, пожалуйста, посмотрите на код библиотеки urwid . В частности, monitored_list.MonitoredList реализует нечто подобное.

В дополнение к этому вы можете захотеть взглянуть на альтернативные способы определения свойств в своем коде, которые предоставляют события уведомления при обновлении свойств, например traits или gobject ( действительно старый учебник, но все же хорошо, чтобы получить идею).

2 голосов
/ 06 декабря 2011

Это можно сделать - я могу придумать сложный способ, когда каждое присвоение «защищенному» свойству создает динамический класс-обертку для хранимого объекта.Этот класс будет контролировать доступ к любому «магическому» методу на объекте (то есть __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
...