在使用 lru_cache 装饰器时将不同的参数模式视为相同的调用
Treat distinct argument patterns while using lru_cache decorator as the same calls
如 official documentation 中所述,Python 的 functools.lru_cache
装饰器将不同的参数模式解释为完全不同的缓存键。例如:
import functools
@functools.lru_cache(maxsize=128)
def test(a, b, *, c, d):
print(f'Hello from function: ({a}, {b}, {c}, {d})')
test(1, 2, c=42, d='answer')
test(1, 2, d='answer', c=42)
test(b=2, a=1, c=42, d='answer')
虽然实际上所有函数调用都是相同的,但上面的代码会产生以下输出:
Hello from function: (1, 2, 42, answer)
Hello from function: (1, 2, 42, answer)
Hello from function: (1, 2, 42, answer)
什么是更“pythonic”的方法来克服将此类调用视为相同的问题?
我对python不太熟悉,我能想到的方法是添加另一个装饰器来对传递给lru_cache
的参数进行排序。
#!/usr/bin/env python3
from functools import update_wrapper, lru_cache
import inspect
def sorted_params(func):
sig = inspect.signature(func, follow_wrapped=True)
def wrapper(*args, **kwargs):
b = sig.bind(*args, **kwargs)
return func(*b.args, **b.kwargs)
return update_wrapper(wrapper, func)
@lru_cache
def test1(a, b, *, c, d):
print(f'Hello from test1: ({a}, {b}, {c}, {d})')
@sorted_params
@lru_cache
def test2(a, b, *, c, d):
print(f'Hello from test2: ({a}, {b}, {c}, {d})')
for func in [test1, test2]:
print('-'*30)
func(1, 2, c=42, d='answer')
func(1, 2, d='answer', c=42)
func(b=2, a=1, c=42, d='answer')
输出:
------------------------------
Hello from test1: (1, 2, 42, answer)
Hello from test1: (1, 2, 42, answer)
Hello from test1: (1, 2, 42, answer)
------------------------------
Hello from test2: (1, 2, 42, answer)
在另一个装饰器中使用 inspect.signature
规范化参数模式:
import functools
import inspect
def normalize_arg_patterns(func):
sig = inspect.signature(func)
@functools.wraps(func)
def _func(*args, **kwargs):
ba = sig.bind(*args, **kwargs)
args, kwargs = ba.args, ba.kwargs
return func(*args, **kwargs)
return _func
用法:
@normalize_arg_patterns # Add this
@functools.lru_cache(maxsize=128)
def test(a, b, *, c, d):
print(f'Hello from function: ({a}, {b}, {c}, {d})')
test(1, 2, c=42, d='answer')
test(1, 2, d='answer', c=42)
test(b=2, a=1, c=42, d='answer')
Python 3.2 和 2.7
在另一个装饰器中使用 inspect.getcallargs
规范化参数模式:
import functools
import inspect
def normalize_arg_patterns(func):
f = inspect.unwrap(func)
@functools.wraps(func)
def _func(*args, **kwargs):
args, kwargs = (), inspect.getcallargs(f, *args, **kwargs)
kwargs = dict(sorted(kwargs.items()))
return func(*args, **kwargs)
return _func
如 official documentation 中所述,Python 的 functools.lru_cache
装饰器将不同的参数模式解释为完全不同的缓存键。例如:
import functools
@functools.lru_cache(maxsize=128)
def test(a, b, *, c, d):
print(f'Hello from function: ({a}, {b}, {c}, {d})')
test(1, 2, c=42, d='answer')
test(1, 2, d='answer', c=42)
test(b=2, a=1, c=42, d='answer')
虽然实际上所有函数调用都是相同的,但上面的代码会产生以下输出:
Hello from function: (1, 2, 42, answer)
Hello from function: (1, 2, 42, answer)
Hello from function: (1, 2, 42, answer)
什么是更“pythonic”的方法来克服将此类调用视为相同的问题?
我对python不太熟悉,我能想到的方法是添加另一个装饰器来对传递给lru_cache
的参数进行排序。
#!/usr/bin/env python3
from functools import update_wrapper, lru_cache
import inspect
def sorted_params(func):
sig = inspect.signature(func, follow_wrapped=True)
def wrapper(*args, **kwargs):
b = sig.bind(*args, **kwargs)
return func(*b.args, **b.kwargs)
return update_wrapper(wrapper, func)
@lru_cache
def test1(a, b, *, c, d):
print(f'Hello from test1: ({a}, {b}, {c}, {d})')
@sorted_params
@lru_cache
def test2(a, b, *, c, d):
print(f'Hello from test2: ({a}, {b}, {c}, {d})')
for func in [test1, test2]:
print('-'*30)
func(1, 2, c=42, d='answer')
func(1, 2, d='answer', c=42)
func(b=2, a=1, c=42, d='answer')
输出:
------------------------------
Hello from test1: (1, 2, 42, answer)
Hello from test1: (1, 2, 42, answer)
Hello from test1: (1, 2, 42, answer)
------------------------------
Hello from test2: (1, 2, 42, answer)
在另一个装饰器中使用 inspect.signature
规范化参数模式:
import functools
import inspect
def normalize_arg_patterns(func):
sig = inspect.signature(func)
@functools.wraps(func)
def _func(*args, **kwargs):
ba = sig.bind(*args, **kwargs)
args, kwargs = ba.args, ba.kwargs
return func(*args, **kwargs)
return _func
用法:
@normalize_arg_patterns # Add this
@functools.lru_cache(maxsize=128)
def test(a, b, *, c, d):
print(f'Hello from function: ({a}, {b}, {c}, {d})')
test(1, 2, c=42, d='answer')
test(1, 2, d='answer', c=42)
test(b=2, a=1, c=42, d='answer')
Python 3.2 和 2.7
在另一个装饰器中使用 inspect.getcallargs
规范化参数模式:
import functools
import inspect
def normalize_arg_patterns(func):
f = inspect.unwrap(func)
@functools.wraps(func)
def _func(*args, **kwargs):
args, kwargs = (), inspect.getcallargs(f, *args, **kwargs)
kwargs = dict(sorted(kwargs.items()))
return func(*args, **kwargs)
return _func