Python 类成员懒初始化

31 投票
5 回答
39856 浏览
提问于 2025-04-17 18:00

我想知道在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

5 个回答

8

另一种让代码更简洁的方法是写一个包装函数,这个函数可以执行你想要的逻辑:

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

需要注意的是,这样做只会为函数的任何一组参数缓存一个值,所以如果你想根据输入值计算不同的结果,你就需要让 memoize 变得稍微复杂一些。

23

这个回答是针对普通的实例属性/方法,而不是类属性、classmethod或者staticmethod

对于Python 3.8及以上版本,可以考虑使用cached_property装饰器,它可以缓存结果。

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

对于Python 3.2及以上版本,可以同时使用propertylru_cache装饰器,后者也可以缓存结果。

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.的回答

46

你可以在@property上使用元类来实现:

class MyMetaClass(type):
    @property
    def my_data(cls):
        if getattr(cls, '_MY_DATA', None) is None:
            my_data = ...  # costly database call
            cls._MY_DATA = my_data
        return cls._MY_DATA


class MyClass(metaclass=MyMetaClass):
    # ...

这样一来,my_data就成了这个类的一个属性,所以昂贵的数据库调用会被推迟,直到你尝试访问MyClass.my_data的时候。数据库调用的结果会被缓存,存储在MyClass._MY_DATA中,这个调用只会为这个类执行一次

对于Python 2,你需要使用class MyClass(object):,并在类定义的主体中添加一个__metaclass__ = MyMetaClass属性来关联元类。

示例:

>>> class MyMetaClass(type):
...     @property
...     def my_data(cls):
...         if getattr(cls, '_MY_DATA', None) is None:
...             print("costly database call executing")
...             my_data = 'bar'
...             cls._MY_DATA = my_data
...         return cls._MY_DATA
... 
>>> class MyClass(metaclass=MyMetaClass):
...     pass
... 
>>> MyClass.my_data
costly database call executing
'bar'
>>> MyClass.my_data
'bar'

之所以这样有效,是因为像property这样的数据描述符会在对象的父类型上查找;对于类来说,父类型是type,而type可以通过使用元类来扩展。

撰写回答