2010-07-13 4 views
7

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

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 Извините за это, я забыл упомянуть, что хочу, чтобы свойство было только для чтения.

+0

Это может быть дубликатом моего вопроса: [Python ленивого свойства декоратор] (http://stackoverflow.com/questions/3012421/python-lazy-property-decorator) – detly

+0

Вы правы это. Не видел его в окне предложений. Голосование закрывается. –

ответ

14

Denis Otkidach's 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__.)

Если вам не нужна или не хотите эту способность произвести перерасчет, тогда следующие (на основе Mike Boers' 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 
+0

Спасибо за это. Я только что редактировал свой вопрос, чтобы добавить требование, которое я забыл. Как я могу сделать это только для чтения? –