为什么Python装饰器不能跨定义链接?

时间:2010-01-19 14:09:28

标签: python decorator

为什么以下两个脚本不等同?

(取自另一个问题:Understanding Python Decorators

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异常 - 如果异常是暂时的(例如Timeout),它会在稍微休眠后重新调用该函数。

我还有一个modifies_db装饰器,它负责一些与缓存相关的内务处理。 modifies_db装饰有retry,因此我假设所有用modifies_db修饰的函数也会隐式重试。我哪里出错了?

3 个答案:

答案 0 :(得分: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

答案 1 :(得分: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

答案 2 :(得分:0)

问题是将“makeitalic”(带一个参数)替换为“makebold”中的“wrapped”函数,该函数接受零参数。

使用*args, **kwargs在链中进一步传递参数:

def wrapped(*args, **kwargs):
    return "<b>" + fn(*args, **kwargs) + "</b>"