Python decorator handles default arguments of the decorated function
我想为类方法创建一个"cache"修饰器,它在一个内部类属性中注册该方法的结果,以避免多次计算它(我不想使用在
第一个想法是创建一个类似于以下内容的装饰器"缓存":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def cache(func): name ="_{:s}".format(func.__name__) def wrapped(obj): if not hasattr(obj, name) or getattr(obj, name) is None: print"Computing..." setattr(obj, name, func(obj)) else: print"Already computed!" return getattr(obj, name) return wrapped class Test: @cache def hello(self): return 1000 ** 5 |
一切正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | In [121]: t = Test() In [122]: hasattr(t, '_hello') Out[122]: False In [123]: t.hello() Computing... Out[123]: 1000000000000000 In [124]: t.hello() Already computed! Out[124]: 1000000000000000 In [125]: hasattr(t, '_hello') Out[125]: True |
号
现在让我们说我想做同样的事情,但是当方法可以用参数(关键字和/或不是)来调用时。当然,现在我们将结果存储在不同的属性中(名称是什么?…),但在字典中,其键由*args和*kwargs组成。让我们用元组来完成它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | def cache(func): name ="_{:s}".format(func.__name__) def wrapped(obj, *args, **kwargs): if not hasattr(obj, name) or getattr(obj, name) is None: setattr(obj, name, {}) o = getattr(obj, name) a = args + tuple(kwargs.items()) if not a in o: print"Computing..." o[a] = func(obj, *args, **kwargs) else: print"Already computed!" return o[a] return wrapped class Test: @cache def hello(self, *args, **kwargs): return 1000 * sum(args) * sum(kwargs.values()) In [137]: t = Test() In [138]: hasattr(t, '_hello') Out[138]: False In [139]: t.hello() Computing... Out[139]: 0 In [140]: hasattr(t, '_hello') Out[140]: True In [141]: t.hello(3) Computing... Out[141]: 0 In [142]: t.hello(p=3) Computing... Out[142]: 0 In [143]: t.hello(4, y=23) Computing... Out[143]: 92000 In [144]: t._hello Out[144]: {(): 0, (3,): 0, (4, ('y', 23)): 92000, (('p', 3),): 0} |
由于方法
1 2 3 4 5 6 7 | In [146]: t.hello(2, a=23,b=34) Computing... Out[146]: 114000 In [147]: t.hello(2, b=34, a=23) Already computed! Out[147]: 114000 |
。
我的问题是:如果该方法有默认参数,那么它将不再工作:
1 2 3 4 | class Test: @cache def hello(self, a=5): return 1000 * a |
现在它不再工作了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | In [155]: t = Test() In [156]: t.hello() Computing... Out[156]: 5000 In [157]: t.hello(a=5) Computing... Out[157]: 5000 In [158]: t.hello(5) Computing... Out[158]: 5000 In [159]: t._hello Out[159]: {(): 5000, (5,): 5000, (('a', 5),): 5000} |
。
结果计算3次,因为参数的给定方式不同(即使它们是"相同"参数!).
有人知道如何在装饰器内部捕获为函数给定的"默认"值吗?
谢谢你
如果您使用的是最新版本的python,那么可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import inspect def cache(func): name ="_{:s}".format(func.__name__) sig = inspect.signature(func) def wrapped(obj, *args, **kwargs): cache_dict = getattr(obj, name, None) if cache_dict is None: cache_dict = {} setattr(obj, name, cache_dict) bound_args = sig.bind(obj, *args, **kwargs) bound_args.apply_defaults() cache_key = tuple(bound_args.arguments.values()) if not cache_key in cache_dict: print("Computing...") cache_dict[cache_key] = func(obj, *args, **kwargs) else: print("Already computed!") return cache_dict[cache_key] return wrapped |
请注意,我重命名了您的
在python 3.3中添加了
根据参数的函数结构有多复杂,可以有多种解决方案。我喜欢的解决方案是在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Test: def hello(self, a=5): @cache def hello(self, a): return 1000 * a return hello(self, a) t = Test() t.hello() t.hello(a=5) t.hello(5) t._hello Out[111]: Computing... Already computed! Already computed! {(5,): 5000} |
另一种方法是在decorator中添加对默认变量的检查,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | def cache(func): name ="_{:s}".format(func.__name__) def wrapped(obj, *args, **kwargs): if not hasattr(obj, name) or getattr(obj, name) is None: setattr(obj, name, {}) o = getattr(obj, name) a = args + tuple(kwargs.items()) if func.func_defaults: # checking if func have default variable for k in kwargs.keys(): if k in func.func_code.co_varnames and kwargs[k] == func.func_defaults[0]: a = () if args: if args[0] == func.func_defaults[0]: a = () if not a in o: print"Computing..." o[a] = func(obj, *args, **kwargs) else: print"Already computed!" return o[a] return wrapped class Test: @cache def hello(self, a=5): return 1000 * a t = Test() t.hello() t.hello(a=5) t.hello(5) t._hello Out[112]: Computing... Already computed! Already computed! {(): 5000} |
号
如果您有,例如2个默认变量,第一个代码(带有内部函数)仍然可以工作,而第二个代码需要在"默认变量检查规则"中进行更改。