Python类成员延迟初始化(Python class member lazy initializa

2019-08-17 08:09发布

我想知道什么是初始化一个类的成员,但只有当访问它,如果访问的蟒蛇方式。 我想下面的代码,它工作正常,但有什么比简单?

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

Answer 1:

你可以使用@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可以使用元类进行扩展。



Answer 2:

使代码清洁器的另一种方法是编写一个包装函数,它的期望的逻辑:

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有点复杂。



Answer 3:

这个答案是一个典型的实例属性/方法而已 ,不是一类属性/ classmethod ,或staticmethod

如何同时使用propertylru_cache装饰? 后者memoizes。

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

请注意,这需要Python≥3.2。

信用: 答案由马克西姆R.



Answer 4:

考虑PIP-安装Dickens包,它可用于在Python 3.5+。 它有一个descriptors包,其提供有关cachedpropertycachedclassproperty装饰,其中使用的在下面的示例被示出。 这似乎按预期方式工作。

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'


Answer 5:

Ringlru_cache样的接口,但任何一种描述符的工作支持: https://ring-cache.readthedocs.io/en/latest/quickstart.html#method-classmethod-staticmethod

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

请参阅链接查看更多细节。



文章来源: Python class member lazy initialization