@lru_cache装饰器过多的缓存未命中

如何lru_cache根据接收到的实际值而不是函数的调用方式来配置其缓存?

>>> from functools import lru_cache
>>> @lru_cache
... def f(x=2):
...     print("reticulating splines...")
...     return x ** 2
...
>>> f()
reticulating splines...
4
>>> f(2)
reticulating splines...
4
>>> f(x=2)
reticulating splines...
4

换句话说,只有上面的第一个调用应该是缓存未命中,其他两个应该是缓存命中。

回答

为此,您必须经历将参数绑定到形式参数的过程。这样做的实际过程是在没有公共接口的 C 代码中实现的,但在inspect. 这比functools.lru_cache正常使用慢了大约 100 倍:

import functools
import inspect

def mycache(f=None, /, **kwargs):
    def inner(f):
        sig = inspect.signature(f)
        f = functools.lru_cache(**kwargs)(f)
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
            return f(*bound.args, **bound.kwargs)
        return wrapper
    if f:
        return inner(f)
    return inner

@mycache
def f(x):
    print("reticulating splines...")
    return x ** 2

如果该方法的性能损失太大,您可以改用以下技巧,这需要更多的代码重复,但运行速度要快得多,仅比lru_cache正常使用慢 2倍(有时更快,使用关键字参数):

@functools.lru_cache
def _f(x):
    print("reticulating splines...")
    return x ** 2

def f(x=2):
    return _f(x)

这使用更快的 C 级参数绑定来规范化对记忆化辅助函数的调用,但需要将函数的参数复制 3 次:一次在外部函数的签名中,一次在助手的签名中,一次在对助手的调用中.


以上是@lru_cache装饰器过多的缓存未命中的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>