没有参数装饰器的装饰器?

1 投票
2 回答
816 浏览
提问于 2025-04-16 03:18

我写了一个装饰器,代码大概是这样的:

def login_required_message(*args, **kwargs):
    kwargs.setdefault('message', "You must be logged in to do that.")
    return _user_passes_test_message(lambda u: u.is_authenticated(), *args, **kwargs)

但是当我尝试在后面不加()来使用它时,就会出错,除非我把它改成这样:

def login_required_message(function=None, *args, **kwargs):
    kwargs.setdefault('message', "You must be logged in to do that.")
    decorator = _user_passes_test_message(lambda u: u.is_authenticated(), *args, **kwargs)
    if function: return decorator(function)
    else: return decorator

这样的话,()就是可选的。那么,我该怎么把这个“可选”的功能放进装饰器里,这样我就可以装饰我的装饰器,让它们可以不带参数呢?

2 个回答

1

这个回答的灵感来源于《Python Cookbook》的第9.6个食谱。

def mydecorator_with_opt_args(func=None, arg1="arg1-default", arg2="arg2-default"):
    if func is None:
        return partial(mydecorator_with_opt_args, arg1=arg1, arg2=arg2)
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Do something with {arg1} and {arg2}")
        return func(*args, **kwargs)

    return wrapper

使用方法:

@mydecorator_with_opt_args(arg1="arg1-set", arg2="arg2-set")
def passer():
    pass
passer()

将会输出

"Do something with arg1-set and arg2-set"

@mydecorator_with_opt_args
def passer2():
    pass
passer2()

将会输出

"Do something with arg1-default and arg2-default"

如果你不传参数(passer2),会发生什么呢?加上一个装饰器就像是在写 passer2 = mydecorator_with_opt_args(passer2),这意味着 if func is None 这一部分会被忽略,你会用默认参数来应用这个装饰器。

但是如果你传了参数(passer),加上一个装饰器就像是在写 passer = mydecorator_with_opt_args(arg1="arg1-set", arg2="arg2-set")(passer)

mydecorator_with_opt_args(arg1="arg1-set", arg2="arg2-set") 会返回 partial(mydecorator_with_opt_args, arg1="arg1-set", arg2="arg2-set"),然后 partial(mydecorator_with_opt_args, arg1="arg1-set", arg2="arg2-set")(passer) 相当于 mydecorator_with_opt_args(func=passer, arg1="arg1-set", arg2="arg2-set")

注意,你需要指定关键字参数。

3

我最近写了一篇博客文章,我觉得它可能能帮到你。为了记录下来,这里是我想到的内容:

def opt_arguments(func):
    def meta_wrapper(*args, **kwargs):
        if len(args) == 1 and callable(args[0]):
            return func(args[0])
        else:
            def meta_func(inner_func):
                return func(inner_func, *args, **kwargs)
            return meta_func
    return meta_wrapper

顺便说一下,在我努力弄明白怎么做的过程中,我发现这件事几乎总是比必要的要复杂得多;-)

撰写回答