代码之家  ›  专栏  ›  技术社区  ›  detly

Python记忆/延迟查找属性修饰符

  •  102
  • detly  · 技术社区  · 15 年前

    最近我浏览了一个包含许多类的现有代码库,其中实例属性反映了存储在数据库中的值。我已经重构了很多这样的属性,使它们的数据库查询被推迟,即不在构造函数中初始化,而只是在第一次读取时。这些属性在实例的生命周期内不会改变,但是它们是第一次计算的真正瓶颈,并且只有在特殊情况下才真正被访问。因此,也可以在从数据库中检索它们之后对它们进行缓存(因此这符合 回忆录 输入只是“无输入”)。

    我发现自己一遍又一遍地为不同类的不同属性键入以下代码片段:

    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中,是否有一个我根本不知道的现有的装饰器来完成这项工作?或者,有没有一个合理简单的方法来定义一个这样做的装饰者?

    我在Python2.5下工作,但是如果2.6的答案有显著的不同,那么它们可能仍然很有趣。

    8 回复  |  直到 9 年前
        1
  •  12
  •   Guy    8 年前

    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)
    
        2
  •  125
  •   Aran-Fey Kevin    7 年前

    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]
    
        3
  •  111
  •   Aran-Fey Kevin    7 年前

    一次性

    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 班级是a non-data descriptor ,这意味着它是只读的。添加 __set__ 方法将阻止它正常工作。

        4
  •  4
  •   gnr    14 年前

    __call__ 你也可以在网上复制 __name__ , __doc__ __module__ 从func的命名空间:

    import time
    
    class Lazyproperty(object):
    
        def __init__(self, timeout=None):
            self.timeout = timeout
            self._cache = {}
    
        def __call__(self, func):
            self.func = func
            return self
    
        def __get__(self, obj, objcls):
            if obj not in self._cache or \
              (self.timeout and time.time() - self._cache[key][1] > self.timeout):
                self._cache[obj] = (self.func(obj), time.time())
            return self._cache[obj]
    

    class Foo(object):
    
        @Lazyproperty(10)
        def bar(self):
            print('calculating')
            return 'bar'
    
    >>> x = Foo()
    >>> print(x.bar)
    calculating
    bar
    >>> print(x.bar)
    bar
    ...(waiting 10 seconds)...
    >>> print(x.bar)
    calculating
    bar
    
        5
  •  3
  •   Ignacio Vazquez-Abrams    15 年前

    property 是一个班级。A descriptor 准确地说。简单地从中派生并实现所需的行为。

    class lazyproperty(property):
       ....
    
    class testA(object):
       ....
      a = lazyproperty('_a')
      b = lazyproperty('_b')
    
        6
  •  3
  •   Antti Haapala -- Слава Україні    9 年前

    想要是最重要的 reify (source linked!)

    用作类方法装饰器。它的运行方式几乎与Python完全相同 @property decorator,但它在第一次调用后将其修饰的方法的结果放入实例dict中,从而有效地用实例变量替换其修饰的函数。用Python的说法,它是一个非数据描述符。下面是一个示例及其用法:

    >>> from pyramid.decorator import reify
    
    >>> class Foo(object):
    ...     @reify
    ...     def jammy(self):
    ...         print('jammy called')
    ...         return 1
    
    >>> f = Foo()
    >>> v = f.jammy
    jammy called
    >>> print(v)
    1
    >>> f.jammy
    1
    >>> # jammy func not called the second time; it replaced itself with 1
    >>> # Note: reassignment is possible
    >>> f.jammy = 2
    >>> f.jammy
    2
    
        7
  •  1
  •   Jason Herbburn    10 年前

    延迟评估只是意味着在运行时需要某个值时,在最后一个可能的时刻对某个值进行评估。 标准 @property 装饰师就是这么做的。 (*)仅在每次需要该属性的值时才对修饰函数求值(参见维基百科关于懒惰评估的文章)

    回忆录是询问者寻找的正确术语。不依赖于返回值评估的副作用的纯函数可以被安全地记忆,并且实际上有一个decorator functools @functools.lru_cache

        8
  •  0
  •   rezakamalifard    8 年前

    class cached_property(property):
        def __init__(self, func, name=None, doc=None):
            self.__name__ = name or func.__name__
            self.__module__ = func.__module__
            self.__doc__ = doc or func.__doc__
            self.func = func
    
        def __set__(self, obj, value):
            obj.__dict__[self.__name__] = value
    
        def __get__(self, obj, type=None):
            if obj is None:
                return self
            value = obj.__dict__.get(self.__name__, None)
            if value is None:
                value = self.func(obj)
                obj.__dict__[self.__name__] = value
            return value
    

    class SampleClass():
        @cached_property
        def cached_property(self):
            print('I am calculating value')
            return 'My calculated value'
    
    
    c = SampleClass()
    print(c.cached_property)
    print(c.cached_property)
    c.cached_property = 2
    print(c.cached_property)
    print(c.cached_property)
    

    值只在第一次计算,然后我们使用保存的值

    I am calculating value
    My calculated value
    My calculated value
    2
    2