如何构建带可选参数的装饰器?

55 投票
5 回答
39435 浏览
提问于 2025-04-16 05:28

我想做一个装饰器,这个装饰器可以带参数使用,也可以不带参数使用。大概是这样的:

class d(object):
    def __init__(self,msg='my default message'):
        self.msg = msg
    def __call__(self,fn):
        def newfn():
            print self.msg
            return fn()
        return newfn

@d('This is working')
def hello():
    print 'hello world !'

@d
def too_bad():
    print 'does not work'

在我的代码中,只有带参数的装饰器能正常工作。那么我该怎么做才能让这两种用法都能正常工作呢?

5 个回答

19

这样做是可以的。

def d(arg):
    if callable(arg):  # Assumes optional argument isn't.
        def newfn():
            print('my default message')
            return arg()
        return newfn
    else:
        def d2(fn):
            def newfn():
                print(arg)
                return fn()
            return newfn
        return d2

@d('This is working')
def hello():
    print('hello world !')

@d  # No explicit arguments will result in default message.
def hello2():
    print('hello2 world !')

@d('Applying it twice')
@d('Would also work')
def hello3():
    print('hello3 world !')

hello()
hello2()
hello3()

输出结果:

This is working
hello world !
my default message
hello2 world !
Applying it twice
Would also work
hello3 world !

如果一个装饰器函数用@调用时没有传入任何明确的参数,它会使用下面定义的函数来调用。如果传入了参数,那么它会先用这些参数进行调用,然后再用这个初步调用的结果(这个结果也必须是可以调用的)来调用正在定义的函数。不管是哪种情况,最后一次或唯一一次调用的返回值都会绑定到定义的函数名称上。

29

如果你想给你的装饰器传递参数,你需要始终把它当作一个函数来调用:

@d()
def func():
    pass

否则,你就得尝试去判断参数的不同,也就是说,你需要神奇地猜测调用者的意思。不要创建一个需要猜测的接口;一开始就清楚地表达你的意思。

换句话说,一个函数要么是装饰器,要么是装饰器工厂;它不应该同时是两者。

注意,如果你只是想存储一个值,其实你不需要写一个类。

def d(msg='my default message'):
    def decorator(func):
        def newfn():
            print msg
            return func()
        return newfn
    return decorator

@d('This is working')
def hello():
    print 'hello world !'

@d()
def hello2():
    print 'also hello world'
59

我找到一个例子,你可以使用 @trace 或者 @trace('msg1','msg2'):挺不错的!

def trace(*args):
    def _trace(func):
        def wrapper(*args, **kwargs):
            print enter_string
            func(*args, **kwargs)
            print exit_string
        return wrapper
    if len(args) == 1 and callable(args[0]):
        # No arguments, this is the decorator
        # Set default values for the arguments
        enter_string = 'entering'
        exit_string = 'exiting'
        return _trace(args[0])
    else:
        # This is just returning the decorator
        enter_string, exit_string = args
        return _trace

撰写回答