【问题标题】:Python: How can I ignore a special parameter in functools.lru_cache?Python:如何忽略 functools.lru_cache 中的特殊参数?
【发布时间】:2023-03-13 20:33:01
【问题描述】:

我要缓存的函数是这样的:

def a(x, time='last'):

我对每个a(x,y) 都有确定性行为,y=='last' 除外。所以当a(x, 'last')被调用时,我想调用“真实的东西”和一个lru_cached函数来处理其他所有的事情。

我想这可以用我自己的装饰器来实现:

def my_lru_cache(func):
    def function_wrapper(*args, **kwargs):
        if kwargs is not None:
            if 'time' in kwargs:
                return func(*args, **kwargs)
            else:
                return what?!?

    return function_wrapper

我完全错了吗?这怎么可能?

【问题讨论】:

  • kwargs 永远不会是 None,它总是会是一本字典,可能是空的。
  • 测试不应该是if 'time' not in kwargs or kwargs['time'] == 'last':,所以要么time没有明确设置(并且默认time='last'适用)要么time='last'被明确调用。
  • 如果你需要支持a(x, 'last')too,那么你还需要检查args,因为现在'last'的值是作为位置参数传入的!

标签: python decorator


【解决方案1】:

将函数包装在lru_cache() 中,然后在顶部添加装饰器并通过__wrapped__ 属性访问原始未缓存的函数,或者更好的是,使用inspect.unwrap() function 剥离任意数量的装饰器的函数:

from functools import wraps
from inspect import unwrap

def bypass_cache_last_time(func):
    @wraps(func)
    def function_wrapper(*args, **kwargs):
        if not 'time' in kwargs or kwargs['time'] == 'last':
            # Bypass any additional decorators and call function directly
            return unwrap(func)(*args, **kwargs)
        else:
            return func(*args, **kwargs)

        return function_wrapper

并将其用作

@bypass_cache_last_time
@lru_cache()
def some_function(x, time='last'):
    # ...

functools.wraps() 装饰器传递了再次向前展开装饰器的能力,因为它在包装器上设置了__wrapped__ 属性。

或者让你的装饰器应用lru_cache()装饰器本身并在装饰时保留你自己的原始函数副本:

def my_lru_cache(func):
    cached = lru_cache()(func)

    @wraps(func)
    def function_wrapper(*args, **kwargs):
        if not 'time' in kwargs or kwargs['time'] == 'last':
            # call the function directly
            return func(*args, **kwargs)
        else:
            # use the lru_cache-wrapped version
            return cached(*args, **kwargs)

    return function_wrapper

把它当作

@my_lru_cache
def some_function(x, time='last'):
    # ...

【讨论】:

  • @my_lru_cache() 中真的需要括号吗?
  • @EugeneYarmash:嗯,没有。
  • return function_wrapper 有错误的缩进,不是吗?
  • 谢谢,我认为可行:) 但是有没有办法使用 cache_info() 函数?可能很难,因为有时它在那里,有时不是:s
  • @MarkusGrunwald:很好,是的,第二个示例的包装返回缩进错误。
【解决方案2】:

您可以使用lru_cache(<args>)(func) 直接调用lru_cache() 以获取func 的“打包”版本。然后你可以从你的包装器中返回它:

def my_lru_cache(func):
    caching_func = lru_cache()(func)
    def function_wrapper(*args, **kwargs):        
        if kwargs.get('time') == 'last':
            return func(*args, **kwargs)
        return caching_func(*args, **kwargs)
    return function_wrapper

【讨论】:

    【解决方案3】:

    改进@martijn-pieters 对我的一个用例的回答;在这里,我创建了一个包装器,它适用于任何可以传递 kwarg skip_cache 的函数,以指定是返回缓存值还是再次计算该值。

    from functools import lru_cache, wraps
    
    
    def skippable_lru_cache(maxsize: int = 128, typed: bool = False):
        def wrapper_cache(func):
            cached_func = lru_cache(maxsize=maxsize, typed=typed)(func)
    
            @wraps(func)
            def wrapped_func(*args, **kwargs):
                if 'skip_cache' in kwargs and kwargs['skip_cache'] == True:
                    # call the function directly
                    return func(*args, **kwargs)
                else:
                    # Remove skip_cache from kwargs so that its value doesn't affect stored results
                    try:
                        del kwargs['skip_cache']
                    except:
                        pass
                    # use the lru_cache-wrapped version
                    return cached_func(*args, **kwargs)
    
            wrapped_func.cache_info = cached_func.cache_info
    
            return wrapped_func
    
        return wrapper_cache
    
    
    @skippable_lru_cache(maxsize=128)
    def calc(v1: int, v2: int, skip_cache: bool = False):
        return v1 * v2
    
    
    print('Run 1:')
    for i in range(20):
        print(calc(i, 10))
    print(calc.cache_info())
    
    print('Run 2:')
    for i in range(20):
        print(calc(i, 10, skip_cache=i > 9))
    print(calc.cache_info())
    
    

    结果:

    Run 1:
    0
    10
    20
    30
    40
    50
    60
    70
    80
    90
    100
    110
    120
    130
    140
    150
    160
    170
    180
    190
    CacheInfo(hits=0, misses=20, maxsize=128, currsize=20)
    # All Run 1 calls are supposed to be hitting a miss as they are requesting cached value, but none are cached, generating a cache of size 20
    
    Run 2:
    0
    10
    20
    30
    40
    50
    60
    70
    80
    90
    100
    110
    120
    130
    140
    150
    160
    170
    180
    190
    CacheInfo(hits=10, misses=20, maxsize=128, currsize=20)
    # For Run 2, only first 10 calls are requesting cache value, which shows in CacheInfo as hits=10 while still keeping the cache size at 20 although skip_cache is present with different value. For the second 10 calls, cache is completely being skipped resulting in the calls not registering as hit or miss, nor affecting the cache size
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-25
      • 1970-01-01
      • 2013-12-11
      • 2023-03-22
      • 1970-01-01
      • 2012-08-14
      相关资源
      最近更新 更多