Python memoising/deferred lookup属性装饰器

det*_*tly 106 python decorator

最近,我浏览了一个包含许多类的现有代码库,其中实例属性反映了存储在数据库中的值.我重构了很多这些属性,以便延迟数据库查找,即.不是在构造函数中初始化,而是仅在第一次读取时初始化.这些属性在实例的生命周期内不会发生变化,但它们是第一次计算的真正瓶颈,并且只在特殊情况下才真正访问过.因此,它们也可以在从数据库中检索后进行缓存(这因此符合记忆的定义,其中输入只是"无输入").

我发现自己一遍又一遍地为各种类的各种属性输入以下代码片段:

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
Run Code Online (Sandbox Code Playgroud)

是否有现成的装饰器已经在Python中执行此操作,我根本不知道?或者,是否有一种相当简单的方法来定义装饰器来执行此操作?

我在Python 2.5下工作,但如果它们有显着差异,2.6答案可能仍然很有趣.

注意

在Python包含了大量现成的装饰器之前,人们已经提出了这个问题.我更新了它只是为了更正术语.

Mik*_*ers 123

这是一个惰性属性装饰器的示例实现:

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)
Run Code Online (Sandbox Code Playgroud)

互动环节:

>>> 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]
Run Code Online (Sandbox Code Playgroud)

  • 给定非数据描述符协议,这个使用`__get__`比下面的答案慢得多且不那么优雅 (4认同)
  • 我通常将内部函数命名为与具有前面下划线的外部函数相同.所以"_lazyprop" - 遵循pep 8的"仅限内部使用"理念. (2认同)

Cyc*_*one 108

我为自己写了这个...用于真正的一次性计算的懒惰属性.我喜欢它,因为它避免在对象上粘贴额外的属性,并且一旦激活就不会浪费时间检查属性存在等:

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
Run Code Online (Sandbox Code Playgroud)

注意:lazy_property该类是非数据描述符,这意味着它是只读的.添加__set__方法会阻止其正常工作.

  • 这花了一点时间来理解,但是绝对令人惊叹的答案.我喜欢函数本身如何被它计算的值替换. (7认同)
  • 对于后代:自从(ref [1](http://stackoverflow.com/a/18289908/313063)和[2](http://stackoverflow.com/a/17487613)以来,在其他答案中提出了其他版本的建议./313063)).似乎这是Python Web框架中的流行版本(衍生工具存在于Pyramid和Werkzeug中). (2认同)
  • 我发现这种方法比选择的答案快7.6倍.(2.45μs/ 322 ns)[见ipython notebook](http://nbviewer.ipython.org/gist/croepha/9278416) (2认同)

guy*_*rad 11

对于各种伟大的实用程序,我正在使用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)
Run Code Online (Sandbox Code Playgroud)


Ign*_*ams 5

property是一个类。准确地说是一个描述符。只需从中派生并实现所需的行为即可。

class lazyproperty(property):
   ....

class testA(object):
   ....
  a = lazyproperty('_a')
  b = lazyproperty('_b')
Run Code Online (Sandbox Code Playgroud)