为什么Python装饰器无法跨定义链式调用?
为什么下面这两个脚本不一样呢?
(摘自另一个问题:理解Python装饰器)
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold
@makeitalic
def hello():
return "hello world"
print hello() ## returns <b><i>hello world</i></b>
还有一个被装饰的装饰器:
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
@makebold
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makeitalic
def hello():
return "hello world"
print hello() ## TypeError: wrapped() takes no arguments (1 given)
我为什么想知道这个呢?我写了一个叫做 retry
的装饰器,用来捕捉 MySQLdb 的异常——如果这个异常是暂时的(比如超时),它会在稍等一会儿后重新调用这个函数。
我还有一个叫 modifies_db
的装饰器,它负责一些与缓存相关的维护工作。modifies_db
是用 retry
装饰的,所以我以为所有用 modifies_db
装饰的函数也会自动重试。那我哪里搞错了呢?
3 个回答
0
这个问题是要把“makeitalic”这个函数(它需要一个参数)替换成“makebold”这个函数,而“makebold”是不需要任何参数的。
可以使用 *args, **kwargs
来把参数继续传递下去:
def wrapped(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
1
原因是,makebold
里面的 wrapped()
不接受任何参数。
当你这样使用装饰器时,可能会出现一些问题。不过我会给你一个例子,告诉你怎么实现你想要的效果,稍等一下。
这里有一个你需要的工作示例。
def makebold(rewrap=False):
if rewrap:
def inner(decorator):
def rewrapper(func):
def wrapped(*args, **kwargs):
return "<b>%s</b>" % decorator(func)(*args,**kwargs)
return wrapped
return rewrapper
return inner
else:
def inner(func):
def wrapped(*args, **kwargs):
return "<b>%s</b>" % func(*args, **kwargs)
return wrapped
return inner
@makebold(rewrap=True)
def makeitalic(fn):
def wrapped(*args, **kwargs):
return "<i>%s</i>" % fn(*args, **kwargs)
return wrapped
@makeitalic
def hello():
return "hello world"
@makebold()
def hello2():
return "Bob Dole"
if __name__ == "__main__":
print hello()
print hello2()
makebold
看起来有点复杂,但它展示了如何写一个可以选择性地包裹另一个装饰器的装饰器。
下面是上面脚本的输出结果:
<b><i>hello world</i></b>
<b>Bob Dole</b>
注意,makebold
是唯一一个递归的装饰器。还有,使用上有一个微妙的区别:@makebold()
和 @makeitalic
。
9
第二个例子的问题在于
@makebold
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
它试图装饰的是 makeitalic
这个装饰器,而不是它返回的 wrapped
这个函数。
你可以用下面这样的方式来实现我认为你想要的效果:
def makeitalic(fn):
@makebold
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
在这里,makeitalic
使用 makebold
来装饰 wrapped
。