Как создать декоратор для ленивой инициализации свойства

Я хочу создать декоратор, который работает как свойство, только вызывает декоративную функцию только один раз, а при последующих вызовах всегда возвращает результат первого вызова. Пример:

def SomeClass(object):
    @LazilyInitializedProperty
    def foo(self):
        print "Now initializing"
        return 5

>>> x = SomeClass()
>>> x.foo
Now initializing
5
>>> x.foo
5

Моя идея состояла в том, чтобы написать собственный декоратор для этого. Итак, я начал, и вот как далеко я продвинулся:

class LazilyInitializedProperty(object):
    def __init__(self, function):
        self._function = function

    def __set__(self, obj, value):
        raise AttributeError("This property is read-only")

    def __get__(self, obj, type):
        # problem: where to store the value once we have calculated it?

Как видите, я не знаю, где хранить кешированное значение. Кажется, что самое простое решение - просто поддерживать словарь, но мне интересно, есть ли для этого более элегантное решение.

EDIT Извините за это, я забыл упомянуть, что я хочу, чтобы свойство было доступно только для чтения.


person Björn Pollex    schedule 13.07.2010    source источник
comment
Это может быть дубликатом моего вопроса: декоратор ленивых свойств Python   -  person detly    schedule 13.07.2010
comment
Вы правы это. В ящике для предложений не увидел. Голосование за закрытие.   -  person Björn Pollex    schedule 13.07.2010


Ответы (1)


CachedAttribute Дениса Откидача — это декоратор метода, который делает атрибуты ленивы (вычисляются один раз, доступны много раз). Чтобы сделать его также доступным только для чтения, я добавил метод __set__. Чтобы сохранить возможность пересчета (см. ниже), я добавил метод __delete__:

class ReadOnlyCachedAttribute(object):    
    '''Computes attribute value and caches it in the instance.
    Source: Python Cookbook 
    Author: Denis Otkidach https://stackoverflow.com/users/168352/denis-otkidach
    This decorator allows you to create a property which can be computed once and
    accessed many times. Sort of like memoization
    '''
    def __init__(self, method, name=None):
        self.method = method
        self.name = name or method.__name__
        self.__doc__ = method.__doc__
    def __get__(self, inst, cls): 
        if inst is None:
            return self
        elif self.name in inst.__dict__:
            return inst.__dict__[self.name]
        else:
            result = self.method(inst)
            inst.__dict__[self.name]=result
            return result    
    def __set__(self, inst, value):
        raise AttributeError("This property is read-only")
    def __delete__(self,inst):
        del inst.__dict__[self.name]

Например:

if __name__=='__main__':
    class Foo(object):
        @ReadOnlyCachedAttribute
        # @read_only_lazyprop
        def bar(self):
            print 'Calculating self.bar'  
            return 42
    foo=Foo()
    print(foo.bar)
    # Calculating self.bar
    # 42
    print(foo.bar)    
    # 42
    try:
        foo.bar=1
    except AttributeError as err:
        print(err)
        # This property is read-only
    del(foo.bar)
    print(foo.bar)
    # Calculating self.bar
    # 42

Одна из прекрасных особенностей CachedAttribute (и ReadOnlyCachedAttribute) заключается в том, что если вы del foo.bar, то при следующем доступе к foo.bar значение вычисляется заново. (Это волшебство стало возможным благодаря тому факту, что del foo.bar удаляет 'bar' из foo.__dict__, но свойство bar остается в Foo.__dict__.)

Если вам не нужна или не нужна эта возможность пересчета, то следующее (на основе lazyprop Майка Боэрса) – это более простой способ создания ленивого свойства, доступного только для чтения.

def read_only_lazyprop(fn):
    attr_name = '_lazy_' + fn.__name__
    @property
    def _lazyprop(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)
    @_lazyprop.setter
    def _lazyprop(self,value):
        raise AttributeError("This property is read-only")
    return _lazyprop
person unutbu    schedule 13.07.2010
comment
Спасибо за это. Я только что отредактировал свой вопрос, добавив требование, которое я забыл. Как я могу сделать это только для чтения? - person Björn Pollex; 13.07.2010