装饰链

Decorator chain

我正在构建一些基于 Django 的 @permission_required 装饰器的装饰器。这些装饰器只是用不同的参数调用 permission_required 装饰器。

例如:

def permission_required_ajax(perm):
    return permission_required(perm, raise_exception=True)

这个效果很好,我有几个这样的例子。

问题是我的装饰器根本不接受任何参数:

def special_permission():
    return permission_required_ajax('special_permission')

这行不通。当我用 @special_permission 装饰器装饰函数时,出现以下错误:

TypeError: special_permission() takes 0 positional arguments but 1 was given

我错过了什么?

您还需要致电您的装饰工厂:

@special_permission()
def function():
    # ...

无论@符号后面的表达式产生什么,都被用作装饰器;调用装饰器,传入装饰函数。

换句话说,装饰器是语法糖:

def function():
    # ...
special_permission()(function)

如果您停止 () 调用,该函数将改为传递给您的包装器。

或者,让您的装饰器接受该函数,并将其直接传递给 permission_required_ajax() 调用生成的装饰器:

def special_permission(func):
    return permission_required_ajax('special_permission')(func)

然后不调用就直接使用(现在是装饰器,不是装饰器工厂):

@special_permission  # no () call now
def function():
    # ...

装饰器参数的工作原理

当装饰器没有参数时,它会传递给正在装饰的 function/class。当它有参数时,它首先用参数调用,然后返回的任何东西然后用 function/class 被装饰调用。

回顾一下:

# Case 1, no arguments

@foo
def bar(): pass

函数 foo 将被调用并传递给函数 bar 并且返回的任何内容都会绑定到名称 bar.

# Case 2

@foo(1, 2, 3)
def bar(): pass

调用函数foo传递1、2、3作为参数。然后调用 foo returns 将 bar 函数作为参数传递,它 returns 绑定到名称 bar.

针对您的情况

一个解决方案可能只是在 special_permission 装饰器调用中添加括号...

@special_permission()  # <-- note the parenthesis
def bar(): ...

或者,如果您不喜欢在装饰器的使用中添加括号,它可以实现为:

def special_permission(f):
    return permission_required_ajax('special_permission')(f)