Python类成员延迟初始化

Python类成员延迟初始化,python,lazy-evaluation,lazy-initialization,Python,Lazy Evaluation,Lazy Initialization,我想知道python初始化类成员的方式是什么,但只有在访问它时,如果被访问的话。 我尝试了下面的代码,它正在工作,但有比这更简单的东西吗 class MyClass(object): _MY_DATA = None @staticmethod def _retrieve_my_data(): my_data = ... # costly database call return my_data @classmethod

我想知道python初始化类成员的方式是什么,但只有在访问它时,如果被访问的话。 我尝试了下面的代码,它正在工作,但有比这更简单的东西吗

class MyClass(object):

    _MY_DATA = None

    @staticmethod
    def _retrieve_my_data():
        my_data = ...  # costly database call
        return my_data

    @classmethod
    def get_my_data(cls):
        if cls._MY_DATA is None:
            cls._MY_DATA = MyClass._retrieve_my_data()
        return cls._MY_DATA
您可以改为在上使用:

这使得类上的
myu data
成为一个属性,因此昂贵的数据库调用被推迟,直到您尝试访问
MyClass.myu data
。数据库调用的结果通过将其存储在
MyClass.\u MY\u DATA
中进行缓存,该类只调用一次

对于Python2,使用
类MyClass(object):
并在类定义体中添加一个附加元类

演示:


这是因为在对象的父类型上查找像
property
这样的数据描述符;对于
类型
类型
的类,可以使用元类进行扩展。

另一种使代码更简洁的方法是编写一个包装函数,该函数执行所需的逻辑:

def memoize(f):
    def wrapped(*args, **kwargs):
        if hasattr(wrapped, '_cached_val'):
            return wrapped._cached_val
        result = f(*args, **kwargs)
        wrapped._cached_val = result
        return result
    return wrapped
您可以按如下方式使用它:

@memoize
def expensive_function():
    print "Computing expensive function..."
    import time
    time.sleep(1)
    return 400

print expensive_function()
print expensive_function()
print expensive_function()
哪些产出:

Computing expensive function...
400
400
400
现在,您的类方法如下所示,例如:

class MyClass(object):
        @classmethod
        @memoize
        def retrieve_data(cls):
            print "Computing data"
            import time
            time.sleep(1) #costly DB call
            my_data = 40
            return my_data

print MyClass.retrieve_data()
print MyClass.retrieve_data()
print MyClass.retrieve_data()
输出:

Computing data
40
40
40

请注意,这将为函数的任何一组参数只缓存一个值,因此如果您想根据输入值计算不同的值,则必须使
记忆化
稍微复杂一些。

此答案仅适用于典型的实例属性/方法,不适用于类属性/
classmethod
staticmethod

对于Python3.8+,使用decorator怎么样?它回忆道

from functools import cached_property

class MyClass:

    @cached_property
    def my_lazy_attr(self):
        print("Initializing and caching attribute, once per class instance.")
        return 7**7**8
from functools import lru_cache

class MyClass:

    @property
    @lru_cache()
    def my_lazy_attr(self):
        print("Initializing and caching attribute, once per class instance.")
        return 7**7**8
对于Python3.2+,如何同时使用和装饰器?后者回忆道

from functools import cached_property

class MyClass:

    @cached_property
    def my_lazy_attr(self):
        print("Initializing and caching attribute, once per class instance.")
        return 7**7**8
from functools import lru_cache

class MyClass:

    @property
    @lru_cache()
    def my_lazy_attr(self):
        print("Initializing and caching attribute, once per class instance.")
        return 7**7**8


信贷:根据Maxime R.

考虑可用于Python 3.5+的pip可安装软件包。它有一个
描述符
包,该包提供相关的
缓存属性
缓存类属性
装饰器,其用法如下例所示。它似乎像预期的那样工作

from descriptors import cachedproperty, classproperty, cachedclassproperty

class MyClass:
    FOO = 'A'

    def __init__(self):
        self.bar = 'B'

    @cachedproperty
    def my_cached_instance_attr(self):
        print('Initializing and caching attribute, once per class instance.')
        return self.bar * 2

    @cachedclassproperty
    def my_cached_class_attr(cls):
        print('Initializing and caching attribute, once per class.')
        return cls.FOO * 3

    @classproperty
    def my_class_property(cls):
        print('Calculating attribute without caching.')
        return cls.FOO + 'C'

Ring
提供了类似于
lru\u cache
的接口,但使用任何类型的描述符都支持:


有关更多详细信息,请参阅链接。

请注意,这在实例级别起作用,尽管Etienne希望在类级别有所帮助。看起来你不能很容易地组合属性和类方法。好吧,但我不想缓存特定实例的结果。相反,我想缓存类本身的结果,因为所有实例的值都相同。对不起,我没有看到您以前的注释。@EtienneRouxel:这能解决您的问题吗?还有什么需要我补充的吗?@EtienneRouxel:没错,而且要使
my_data
作为类的属性工作。属性是一个类,因此只有在元类上定义它时,它才会作为类属性工作。
class Page(object):
    (...)

    @ring.lru()
    @classmethod
    def class_content(cls):
        return cls.base_content

    @ring.lru()
    @staticmethod
    def example_dot_com():
        return requests.get('http://example.com').content