将函数的缓存值作为函数的属性存储在 Python 中
Storing cached values of a function as an attribute of the function in Python
我想要 @cached
类似于 @memoized
的装饰器,它将函数的缓存值存储为函数的属性。像这样
def cached(fcn):
def cached_fcn(*args,**kwargs):
call_signature=",".join([repr(a) for a in args] +
[repr(kwa[0])+"="+repr(kwa[1])
for kwa in sorted(kwargs.items()) ])
if call_signature not in cached_fcn.cache:
cached_fcn.cache[call_signature] = fcn(*args,**kwargs)
return copy.deepcopy(cached_fcn.cache[call_signature])
cached_fcn.__name__ = fcn.__name__
cached_fcn.__doc__ = fcn.__doc__
cached_fcn.__annotations__ = fcn.__annotations__
cached_fcn.cache = dict()
return cached_fcn
@cached
def fib(n):
if n in (0,1): return 1
return fin(n-1) + fib(n-2)
假设该函数不访问任何全局内容,这样做安全吗?如果使用线程呢?
有一个陷阱可能与您的实施有关。观察
def pf(*args, **kwargs):
print(args)
print(kwargs)
并用
调用它
pf(1, k="a")
pf(1, "a")
pf(k="a", x=1)
所有参数规范都是具有签名 f(x, k)
的函数的有效规范(有或没有默认值)- 所以你无法真正知道参数的顺序、它们的名称以及在 [=15 上的排序=] 在一般情况下肯定是不够的(在第二个示例中为空,而 args
在最后一个顺序相反的情况下为空)。默认值使情况变得更糟,就好像 f(x, k=3)
是定义,然后 f(2, 3)
和 f(2)
和 f(x=2)
f(2, k=3)
和 f(x=2, k=3)
(也相反)是相同,传递给包装器的 kwargs
和 args
不同。
更强大的解决方案将使用 inspect.getargspec(your_function)
。这使用反射来了解函数定义时的实际参数名称。然后你必须“填写”你在 *args
和 **kwargs
中给出的参数,并使用它来生成你的调用签名:
import inspect
def f(x, k=3): pass
argspec = inspect.getargspec(f) # returns ArgSpec(args=['x', 'k'], varargs=None, keywords=None, defaults=(3,))
现在您可以生成调用签名(来自 *args
和 **kwargs
):
signature = {}
for arg, default in zip(reversed(argspec.args), reversed(argspec.defaults)):
signature[arg] = default
set_args = set()
for arg, val in zip(argspec.args, args):
set_args.add(arg)
signature[arg] = val
for arg, val in kwargs.items():
# if arg in set_args:
# raise TypeError(f'{arg} set both in kwargs and in args!')
# if arg not in argspec.args:
# raise TypeError(f'{arg} is not a valid argument for function!')
signature[arg] = val
# if len(signature) == len(argspec.args):
# raise TypeError(f'Received {len(signature)} arguments but expected {len(argspec.args)} arguments!')
然后你可以使用字典 signature
本身作为调用签名。我在上面展示了一些“正确性”检查,尽管您可能只想让函数调用本身检测并失败。我没有处理带有 **kwargs
和 *args
的函数(实际使用的名称在 argspec
中给出)。我认为他们可能只涉及 signature
中的 args
和 kwargs
键。我仍然不确定上面的内容有多强大。
更好的是,使用内置的 functools.lru_cache
它可以满足您的需求。
关于线程,您面临着与任何时候多个线程访问同一个数组相同的危险。函数属性没有什么特别之处。 lru_cache
应该是安全的(有一个 bug 已解决),但有一个警告:
To help measure the effectiveness of the cache and tune the maxsize
parameter, the wrapped function is instrumented with a cache_info()
function that returns a named tuple showing hits, misses, maxsize and
currsize. In a multi-threaded environment, the hits and misses are
approximate
我想要 @cached
类似于 @memoized
的装饰器,它将函数的缓存值存储为函数的属性。像这样
def cached(fcn):
def cached_fcn(*args,**kwargs):
call_signature=",".join([repr(a) for a in args] +
[repr(kwa[0])+"="+repr(kwa[1])
for kwa in sorted(kwargs.items()) ])
if call_signature not in cached_fcn.cache:
cached_fcn.cache[call_signature] = fcn(*args,**kwargs)
return copy.deepcopy(cached_fcn.cache[call_signature])
cached_fcn.__name__ = fcn.__name__
cached_fcn.__doc__ = fcn.__doc__
cached_fcn.__annotations__ = fcn.__annotations__
cached_fcn.cache = dict()
return cached_fcn
@cached
def fib(n):
if n in (0,1): return 1
return fin(n-1) + fib(n-2)
假设该函数不访问任何全局内容,这样做安全吗?如果使用线程呢?
有一个陷阱可能与您的实施有关。观察
def pf(*args, **kwargs):
print(args)
print(kwargs)
并用
调用它pf(1, k="a")
pf(1, "a")
pf(k="a", x=1)
所有参数规范都是具有签名 f(x, k)
的函数的有效规范(有或没有默认值)- 所以你无法真正知道参数的顺序、它们的名称以及在 [=15 上的排序=] 在一般情况下肯定是不够的(在第二个示例中为空,而 args
在最后一个顺序相反的情况下为空)。默认值使情况变得更糟,就好像 f(x, k=3)
是定义,然后 f(2, 3)
和 f(2)
和 f(x=2)
f(2, k=3)
和 f(x=2, k=3)
(也相反)是相同,传递给包装器的 kwargs
和 args
不同。
更强大的解决方案将使用 inspect.getargspec(your_function)
。这使用反射来了解函数定义时的实际参数名称。然后你必须“填写”你在 *args
和 **kwargs
中给出的参数,并使用它来生成你的调用签名:
import inspect
def f(x, k=3): pass
argspec = inspect.getargspec(f) # returns ArgSpec(args=['x', 'k'], varargs=None, keywords=None, defaults=(3,))
现在您可以生成调用签名(来自 *args
和 **kwargs
):
signature = {}
for arg, default in zip(reversed(argspec.args), reversed(argspec.defaults)):
signature[arg] = default
set_args = set()
for arg, val in zip(argspec.args, args):
set_args.add(arg)
signature[arg] = val
for arg, val in kwargs.items():
# if arg in set_args:
# raise TypeError(f'{arg} set both in kwargs and in args!')
# if arg not in argspec.args:
# raise TypeError(f'{arg} is not a valid argument for function!')
signature[arg] = val
# if len(signature) == len(argspec.args):
# raise TypeError(f'Received {len(signature)} arguments but expected {len(argspec.args)} arguments!')
然后你可以使用字典 signature
本身作为调用签名。我在上面展示了一些“正确性”检查,尽管您可能只想让函数调用本身检测并失败。我没有处理带有 **kwargs
和 *args
的函数(实际使用的名称在 argspec
中给出)。我认为他们可能只涉及 signature
中的 args
和 kwargs
键。我仍然不确定上面的内容有多强大。
更好的是,使用内置的 functools.lru_cache
它可以满足您的需求。
关于线程,您面临着与任何时候多个线程访问同一个数组相同的危险。函数属性没有什么特别之处。 lru_cache
应该是安全的(有一个 bug 已解决),但有一个警告:
To help measure the effectiveness of the cache and tune the maxsize parameter, the wrapped function is instrumented with a cache_info() function that returns a named tuple showing hits, misses, maxsize and currsize. In a multi-threaded environment, the hits and misses are approximate