【问题标题】:Configure lru_cache for class and static methods为类和静态方法配置 lru_cache
【发布时间】:2017-10-03 12:44:58
【问题描述】:

我正在尝试在 Python3 中使用 lru_cache 来加快对 Salesforce 数据库的常见查询。下面是应该的相关代码

  • a) 将不可散列的参数转换为可散列的参数,并且
  • b) 为这些对象启用 LRU 缓存。

当我尝试这段代码时,缓存适用于调用不带参数的函数,但它似乎没有缓存带参数的函数调用。另外,我不确定如何为装饰功能订购装饰器。

注意,我在这里使用了一个带有类和静态方法的类,因此我可以为Resource 的不同子类覆盖getget_all 方法。

请解释我做错了什么或可以做得更好。

from functools import lru_cache
from functools import wraps

class Resource(object):

    def hash_dict(func):
        """Transform mutable dictionnary
           Into immutable
           Useful to be compatible with cache
        """
        class HDict(dict):
            def __hash__(self):
                return hash(frozenset(self.items()))

        @wraps(func)
        def wrapped(*args, **kwargs):
            args = tuple([HDict(arg) if isinstance(arg, dict) else arg for arg in args])
            kwargs = {}
            for k, v in kwargs.items():
                if isinstance(v, dict):
                    kwargs[k] = HDict(v)
                elif isinstance(v, list):
                    kwargs[k] = tuple(v)
                else:
                    kwargs[k] = v
            return func(*args, **kwargs)
        return wrapped

    @staticmethod
    @hash_dict
    @lru_cache
    def get(cls, resource_id, lang='en', fields=None):
        pass

    @classmethod
    @hash_dict
    @lru_cache
    def get_all(cls, lang='en', filters=None, fields=None):
        pass

【问题讨论】:

  • dicts 是故意不可散列的,添加 __hash__ 方法实现并不能使您免于在包装方法内可能发生的 kwargs 突变

标签: python python-3.x lru


【解决方案1】:

不需要额外的包。以下工作按预期进行:

import functools


class A:

  @staticmethod
  @functools.lru_cache(maxsize=None)
  def build(value):
    print('Creating', value)
    return A()


assert A.build('a') is A.build('a')
assert A.build('b') is A.build('b')
assert A.build('b') is not A.build('a')

从 python 3.9 开始,您可以将 @functools.lru_cache(maxsize=None) 替换为 @functools.cache

【讨论】:

    【解决方案2】:

    这应该是您正在寻找的答案:https://ring-cache.readthedocs.io/en/latest/quickstart.html#method-classmethod-staticmethod

    lru_cache 只支持简单的函数。 Ring 提供了非常相似的接口,但包括任何类型的描述符支持。

    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
    

    查看链接了解更多详情。请注意,示例不是 LRU。

    【讨论】:

      【解决方案3】:

      IDK,如果我迟到了,这是我的回应。你搞错了几件事。

      首先,您将覆盖wapped 函数定义中的argskwargs。这实际上是在删除您的函数参数。

      其次,您只在 kwargs 情况下创建不可变列表,而不是在 args 情况下。

      另外,lru_cache 是一个装饰器构造函数,所以它必须被称为@lru_cache()。如果没有它,我不知道它对你有什么影响。

      此外,您将def get(cls, ... 函数声明为@staticmethod,那么它将不会收到cls 参数。

      最重要的是,在类中定义装饰器并不简单,正如this medium article 中所述。 我明白您正在尝试做的事情:通过继承强制缓存,虽然这似乎是个好主意,但它不能正常工作。如果你重写一个函数,你将不得不再次用缓存重新装饰它,这错过了在类中声明装饰器的要点。

      总结一下,我会为自己省去麻烦,并在不同的类中声明装饰器并在其他地方使用它。但要小心,因为缓存类方法也不简单。


      额外:

      几周前我遇到了类似的情况,想要缓存一个接受 numpy 数组的函数。我在this so response 的基础上提出了this implementation。我只是将数组转换为一个元组,然后再次重建它(因为我需要它最后成为一个可变数组)。

      def np_cache(*args, **kwargs):
          """LRU cache implementation for functions whose FIRST parameter is a numpy array
          >>> array = np.array([[1, 2, 3], [4, 5, 6]])
          >>> @np_cache(maxsize=256)
          ... def multiply(array, factor):
          ...     print("Calculating...")
          ...     return factor*array
          >>> multiply(array, 2)
          Calculating...
          array([[ 2,  4,  6],
                 [ 8, 10, 12]])
          >>> multiply(array, 2)
          array([[ 2,  4,  6],
                 [ 8, 10, 12]])
          >>> multiply.cache_info()
          CacheInfo(hits=1, misses=1, maxsize=256, currsize=1)
      
          """
          def decorator(function):
              @wraps(function)
              def wrapper(np_array, *args, **kwargs):
                  hashable_array = array_to_tuple(np_array)
                  return cached_wrapper(hashable_array, *args, **kwargs)
      
              @lru_cache(*args, **kwargs)
              def cached_wrapper(hashable_array, *args, **kwargs):
                  array = np.array(hashable_array)
                  return function(array, *args, **kwargs)
      
              def array_to_tuple(np_array):
                  """Iterates recursivelly."""
                  try:
                      return tuple(array_to_tuple(_) for _ in np_array)
                  except TypeError:
                      return np_array
      
              # copy lru_cache attributes over too
              wrapper.cache_info = cached_wrapper.cache_info
              wrapper.cache_clear = cached_wrapper.cache_clear
      
              return wrapper
      
          return decorator
      

      虽然这不能直接解决您的问题,但它可以很容易地推广到任意数量的输入参数。

      【讨论】:

        猜你喜欢
        • 2016-01-26
        • 1970-01-01
        • 2021-08-11
        • 2019-10-29
        • 1970-01-01
        • 2016-09-25
        • 2011-08-16
        • 2016-07-29
        • 1970-01-01
        相关资源
        最近更新 更多