【问题标题】:Python class member lazy initializationPython 类成员延迟初始化
【发布时间】:2013-02-20 00:38:05
【问题描述】:

我想知道初始化类成员的 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

【问题讨论】:

    标签: python lazy-evaluation lazy-initialization


    【解决方案1】:

    您可以在 the metaclass 上使用 @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 attribute 以附加元类。

    演示:

    >>> 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 这样的数据描述符是在对象的父类型上查找的;对于 typetype 的类,可以使用元类进行扩展。

    【讨论】:

    • 请注意,这适用于实例级别,尽管 Etienne 想要在类级别上有所作为。看来你不能轻易地结合属性和类方法。
    • 好的,但我不想缓存特定实例的结果。相反,我想为类本身缓存结果,因为所有实例的值都是相同的。
    • 我没有看到你以前的cmets,对不起。
    • @EtienneRouxel:这能解决你的问题吗?你还有什么要我补充的吗?
    • @EtienneRouxel:完全正确,并让my_data 作为类的属性工作。属性是data descriptor,因此在元类上定义时作为类属性工作。
    【解决方案2】:

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

    对于 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
    

    图片来源:answer,Maxime R.

    【讨论】:

      【解决方案3】:

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

      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 更复杂一些。

      【讨论】:

        【解决方案4】:

        考虑可用于 Python 3.5+ 的 pip-installable Dickens 包。它有一个 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'
        

        【讨论】:

          【解决方案5】:

          Ring 提供类似lru_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
          

          查看链接了解更多详情。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2023-04-08
            • 1970-01-01
            • 2020-05-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-11-17
            相关资源
            最近更新 更多