我想为类方法创建一个'cache'修饰符,它在一个内部类属性中注册方法的结果,以避免多次计算它(我不想使用一个简单的属性,在__init__
中计算,因为我不确定每次都计算一次)。在
第一个想法是创建一个类似这样的装饰器“缓存”:
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
一切正常:
^{pr2}$现在让我们假设我想做同样的事情,但是当方法可以用参数(关键字和/或不带)调用时。 当然,现在我们将把结果存储在不同的属性中(名称是什么?…),但在字典中,其键由*arg和**kwargs组成。让我们用元组来做:
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}
由于方法items
在元组中转换字典,而不考虑字典中的顺序,因此,如果关键字参数的调用顺序不相同,则可以完美地工作:
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
我的问题是:如果方法有默认参数,那么它就不再工作了:
class Test:
@cache
def hello(self, a=5):
return 1000 * a
现在它不再工作了:
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次,因为参数的给定方式不同(即使它们是“相同”的参数)。在
有人知道我如何在decorator中捕捉给函数的“默认”值吗?在
谢谢你
如果您使用的是最新版本的Python,那么可以使用^{} 获得一个
Signature
对象,该对象完全封装了有关函数参数的信息。然后,可以使用包装器传递的参数调用它的bind
方法,以获得一个BoundArguments
对象。调用BoundArguments
上的apply_defaults
方法来填充任何缺少的具有默认值的参数,并检查arguments
有序字典,以查看函数的参数及其此调用的值的明确列表:请注意,我重命名了您的}这样的调用!在
a
和o
变量,使其具有更有意义的名称。我还改变了在对象上设置缓存字典的方式。更少的getattr
和{在python3.3中添加了
inspect.signature
函数和相关类型,但是在python3.5中,BoundArguments
对象的apply_defaults
方法是新的。旧版本的Python有一个基本功能的后端端口on PyPi,但它似乎还没有包括apply_defaults
。我将把它作为后端口的github tracker上的一个问题来报告。在根据参数函数结构的复杂程度,可能会有不同的解决方案。我喜欢的解决方案是在
hello
中添加内部函数。如果不想更改缓存的名称,请使用外部函数的相同名称:另一种方法是在decorator中添加对默认变量的检查,例如:
^{pr2}$如果你有两个默认变量,第一个代码(带内部函数)仍然可以工作,而第二个代码需要在“默认变量检查规则”中进行更改。在
相关问题 更多 >
编程相关推荐