Python 记忆化/延迟查找属性装饰器
最近,我在处理一个已有的代码库,这里面有很多类,它们的实例属性反映了存储在数据库中的值。我对这些属性进行了重构,让它们的数据库查询延迟执行,也就是说,不在构造函数中初始化,而是在第一次读取时才进行查询。这些属性在实例的生命周期内不会改变,但第一次计算时确实很耗时,而且通常只在特定情况下才会被访问。因此,在从数据库获取这些值后,我们可以将它们缓存起来(这就符合了记忆化的定义,因为输入实际上是“没有输入”)。
我发现自己在不同的类中反复输入以下代码片段,用于处理各种属性:
class testA(object):
def __init__(self):
self._a = None
self._b = None
@property
def a(self):
if self._a is None:
# Calculate the attribute now
self._a = 7
return self._a
@property
def b(self):
#etc
请问在Python中有没有现成的装饰器可以做到这一点,而我却不知道?或者,有没有比较简单的方法来定义一个这样的装饰器?
我现在使用的是Python 2.5,但如果2.6的答案有显著不同,我也会觉得很有趣。
注意
这个问题是在Python添加了很多现成的装饰器之前提出的。我更新了这个问题,只是为了纠正一些术语。
10 个回答
115
我写这个是为了自己用... 用于真正的一次性计算的懒属性。我喜欢这个方法,因为它避免在对象上添加额外的属性,而且一旦激活后,就不会浪费时间去检查属性是否存在等等。
import functools
class lazy_property(object):
'''
meant to be used for lazy evaluation of an object attribute.
property should represent non-mutable data, as it replaces itself.
'''
def __init__(self, fget):
self.fget = fget
# copy the getter function's docstring and other attributes
functools.update_wrapper(self, fget)
def __get__(self, obj, cls):
if obj is None:
return self
value = self.fget(obj)
setattr(obj, self.fget.__name__, value)
return value
class Test(object):
@lazy_property
def results(self):
calcs = 1 # Do a lot of calculation here
return calcs
注意:lazy_property
类是一个非数据描述符,这意味着它是只读的。如果添加一个__set__
方法,就会导致它无法正常工作。
129
这里有一个懒加载属性装饰器的示例实现:
import functools
def lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
@property
@functools.wraps(fn)
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
class Test(object):
@lazyprop
def a(self):
print 'generating "a"'
return range(5)
互动示例:
>>> t = Test()
>>> t.__dict__
{}
>>> t.a
generating "a"
[0, 1, 2, 3, 4]
>>> t.__dict__
{'_lazy_a': [0, 1, 2, 3, 4]}
>>> t.a
[0, 1, 2, 3, 4]
16
为了使用各种很棒的工具,我在用boltons这个库。
这个库里面有一个叫cachedproperty的功能:
from boltons.cacheutils import cachedproperty
class Foo(object):
def __init__(self):
self.value = 4
@cachedproperty
def cached_prop(self):
self.value += 1
return self.value
f = Foo()
print(f.value) # initial value
print(f.cached_prop) # cached property is calculated
f.value = 1
print(f.cached_prop) # same value for the cached property - it isn't calculated again
print(f.value) # the backing value is different (it's essentially unrelated value)