在Python中向装饰器传递参数

7
为什么带参数的装饰器不起作用?
def decAny( f0 ):
    def wrapper( s0 ):
        return "<%s> %s </%s>" % ( any, f0(), any )
    return wrapper

@decAny( 'xxx' )
def test2():
    return 'test1XML'

print( test2() )

总是提示“str不可调用”的错误,它试图在wrapper()内执行返回的字符串,而不是处理并返回结果字符串。


1
这样想一下:在你甚至开始装饰test2之前,你就调用了decAny('xxx')。但是decAny需要一个函数f0,而不是一个字符串。因此很明显,在某个时候,f0()将尝试调用'xxx' - abarnert
好的,但是在没有参数的装饰器中,为什么编译器不会假设第一个参数是客户端函数呢? - ZEE
1
这不是参数的问题。如果你使用 @decAny,那么就是将 decAny 本身作为装饰器。但是如果你使用 @decAny(),那么在装饰之前就会调用 decAny,就像使用 @decAny('xxx') 一样。(这就像当你将函数作为值传递,存储到变量中等,而不是进行调用)。 - abarnert
你正在调用 decAny('xxx')。但是 decAny 接受一个函数 f0,而不是一个字符串。 在装饰器中声明的参数应该传递给客户端函数... 这将简化并增强带参数的装饰器的直观性--- 没有参数的装饰器按预期工作... 问题在于装饰器中参数的定义... 应该更加精细和简化... - ZEE
好的...我想我明白了要点...我会用这些信息再做一些测试... - ZEE
PEP的第一版草案实际上几乎给出了您的确切示例,但在定义类似于您的“decAny”之后,它接着做了像“bold = decAny('b')”,“italic = decAny('i')”等操作,这使您只需使用“@bold”(不带参数)。基本上,装饰器能够像这样工作的原因与仅引用函数返回值而不是调用它的原因相同(与Ruby不同)。 - abarnert
3个回答

16

装饰器是返回函数的函数。当“向装饰器传递参数”时,实际上是调用返回装饰器的函数。因此,decAny() 应该是一个返回函数的函数。

它看起来应该像这样:

import functools

def decAny(tag):
    def dec(f0):
        @functools.wraps(f0)
        def wrapper(*args, **kwargs):
            return "<%s> %s </%s>" % (tag, f0(*args, **kwargs), tag)
        return wrapper
    return dec

@decAny( 'xxx' )
def test2():
    return 'test1XML'

例子:

>>> print(test2())
<xxx> test1XML </xxx>
请注意,除了解决您遇到的具体问题之外,我还通过将*args**kwargs作为包装函数的参数添加并将它们传递给装饰器内部的f0调用来稍微改进了您的代码。 这使得您可以装饰接受任意数量位置或命名参数的函数,并且仍然可以正常工作。
关于functools.wraps(),您可以在这里阅读更多信息:
http://docs.python.org/2/library/functools.html#functools.wraps

1
如果你要添加 *args, **kwargs 来改进他的代码(而不解释原因),那么你可能也想添加 functools.wraps - abarnert
@abarnert 感谢您的建议,已添加 functools.wraps 和一些额外的解释。 - Andrew Clark
这似乎是一个函数工厂...我知道其他语言的装饰器机制,而Python的方式似乎让简单的事情变得复杂了... - ZEE
装饰器指令@decorator假定下一行的“function result”是要装饰的数据... 装饰器的第一个隐式参数是紧跟在装饰器后面的函数... 为什么要把事情搞得这么复杂...编译器/解释器应该将“要包装的函数”作为第一个参数,如果装饰器有参数,则将它们全部传递给“包装器”... 这样可以避免更昂贵、不太直观的工厂模式!!! 我错过了什么吗? - ZEE
从Python语言定义的角度来看,Python装饰器机制确实非常简单:整个装饰器表达式(无论是否带参数)都会被简单地求值,并将结果调用应用于被装饰的对象。(从程序员的角度来看,这使得带参数的装饰器令人困惑。) - Lutz Prechelt
显示剩余2条评论

1

以下是来自《Mark Lutz - Learning Python》一书的良好示例:

def timer(label=''):
    def decorator(func):
        def onCall(*args):   # Multilevel state retention:
            ...              # args passed to function
            func(*args)      # func retained in enclosing scope
            print(label, ... # label retained in enclosing scope
        return onCall
    return decorator         # Returns the actual decorator

@timer('==>')                # Like listcomp = timer('==>')(listcomp)
def listcomp(N): ...         # listcomp is rebound to new onCall

listcomp(...)                # Really calls onCall

0

还有一种使用类实现装饰器的方法,你也可以向装饰器本身传递参数

这里有一个日志助手装饰器的例子,你可以在它失败时传递函数作用域和返回的值

import logging

class LoggerHelper(object):

    def __init__(self, scope, ret=False):
        self.scope = scope
        self.ret = ret

    def __call__(self, original_function):
        def inner_func(*args, **kwargs):
            try:
                logging.info(f"*** {self.scope} {original_function.__name__} Excuting ***")
                return original_function(*args, **kwargs)
                logging.info(f"*** {self.scope} {original_function.__name__} Executed Successfully ***")
            except Exception as e:
                logging.error(f"*** {self.scope} {original_function.__name__} Error: {str(e)} ***")
                return self.ret
            
        return inner_func

当你使用它时,你可以轻松追踪异常的发生位置。

class Example:

    @LoggerHelper("Example", ret=False)
    def method:
        print(success)
        return True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接