如何为属性的懒初始化创建装饰器
我想创建一个装饰器,它的工作方式像属性一样,只不过它只会调用一次被装饰的函数,之后的调用都会返回第一次调用的结果。举个例子:
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?
如你所见,我不知道该把缓存的值存在哪里。最简单的办法似乎是维护一个字典,但我在想有没有更优雅的解决方案。
补充说明 抱歉,我忘了提到我希望这个属性是只读的。
1 个回答
15
Denis Otkidach的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