Python类成员延迟初始化

20 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
    def get_my_data(cls):
        if cls._MY_DATA is None:
            cls._MY_DATA = MyClass._retrieve_my_data()
        return cls._MY_DATA
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 25

您可以在元类@property上使用a :

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

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

这是有效的,因为property在对象的父类型上查找数据描述符; 上课那是type,和type可以使用元类进行扩展.


Acu*_*nus 6

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

如何同时使用装饰器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
Run Code Online (Sandbox Code Playgroud)

请注意,这需要Python?3.2。

信用:Maxime R的回答


Cla*_*diu 5

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

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

您可以按如下方式使用它:

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

print expensive_function()
print expensive_function()
print expensive_function()
Run Code Online (Sandbox Code Playgroud)

哪个输出:

Computing expensive function...
400
400
400
Run Code Online (Sandbox Code Playgroud)

现在你的classmethod看起来如下,例如:

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

输出:

Computing data
40
40
40
Run Code Online (Sandbox Code Playgroud)

请注意,这将为函数的任何参数集缓存一个值,因此,如果要根据输入值计算不同的值,则必须使其memoize更复杂一些.