将另一个装饰器创建为装饰器(Python)

5

在研究了几个小时 Python 中的装饰器后,我仍然有两个问题。

首先,如果你有一个没有参数的装饰器,语法如下:

@decorator
def bye():
    return "bye"

这只是一种语法糖,与 this 关键字一样。

bye = decorator(bye)

但是如果我有一个带参数的装饰器:

@decorator(*args)
def bye():
    return "bye"

“无糖”版本是什么样子的?函数是否作为其中一个参数传递进去了?
bye = decorator("argument", bye)

第二个问题(与第一个有关,但更实际的例子);
def permission_required(permission):
    def wrap(function):
        @functools.wraps(function)
            def wrapped_func(*args, **kwargs):
                if not current_user.can(permission):
                    abort(403)
                return function(*args, **kwargs)
            return wrapped_function
    return wrap

def admin_required(f):
    return permission_required(Permission.ADMINISTER)(f)

这里,permission_required 装饰器被传递给了新创建的装饰器 admin_required 的返回语句中。我不知道这是怎么工作的。主要是返回语句,在这里我们返回原始装饰器+函数(用奇怪的语法)。有人能详细说明吗?非常欢迎提供细节。


2
我认为将带参数的装饰器视为装饰器工厂是很有帮助的。你向工厂传递一些参数,它会返回一个真正的装饰器函数,这个真正的装饰器函数以你要装饰的函数作为唯一的参数。 - PM 2Ring
2个回答

9

当使用装饰器符号表示参数时,

@decorator(a, b, c)
def function(): pass

这是一种语法糖,用于编写代码时简化语法

def function(): pass

function = decorator(a, b, c)(function)

也就是说,decorator 接受参数 a、b、c,并返回一个对象,该对象接受唯一参数 function
当装饰器是一个类时,这样的方式最容易理解。我将使用您的 permission_required 装饰器作为运行示例。它可以这样写:
class permission_required:
    def __init__(self, permission):
        self.permission = permission

    def __call__(self, function):
        @functools.wraps(function)
        def wrapped_func(*args, **kwargs):
            if not current_user.can(permission):
                abort(403)
            return function(*args, **kwargs)
        return wrapped_func

admin_required = permission_required(Permission.ADMINISTER)

当您使用装饰器时,例如:
@permission_required(Permission.DESTRUCTIVE)
def erase_the_database():
    raise NotImplemented # TBD: should we even have this?

您需要首先实例化该类,将Permission.DESTRUCTIVE传递给__init__,然后将实例作为函数调用,并使用erase_the_database作为参数,这将调用__call__方法,构造包装函数并返回它。
按照这种方式考虑,admin_required应该更容易理解:它是permission_required类的一个尚未被调用的实例。 它基本上是一种简写形式:
@admin_required
def add_user(...): ...

不需要手动输入

@permission_required(Permission.ADMINISTER)
def add_user(...): ...

现在,按照您的方式...
def permission_required(permission):
    def wrap(function):
        @functools.wraps(function)
            def wrapped_func(*args, **kwargs):
                if not current_user.can(permission):
                    abort(403)
                return function(*args, **kwargs)
            return wrapped_func
    return wrap

permission_required返回的wrap实际上只是另一种写法而已。因为从permission_required中返回wrap时,它会隐式地创建一个闭包对象,该对象可以像函数一样被调用,当你这样做时,它会调用wrap函数。闭包对象会记住传递给permission_requiredpermission值,以便wrap函数可以使用它。这正是我上面展示的类所做的事情。(事实上,像C++和Rust这样的编译型语言通常通过将闭包解糖成类定义来实现它们。)

请注意,wrap函数本身也在做同样的事情!我们甚至可以进一步扩展它...

class permission_check_wrapper:
    def __init__(self, function, permission):
        self.function = function
        self.permission = permission
        functools.update_wrapper(self, function)

   def __call__(self, *args, **kwargs):
       if not current_user.can(permission):
           abort(403)
        return function(*args, **kwargs)

class permission_required:
    def __init__(self, permission):
        self.permission = permission

    def __call__(self, function):
        return permission_check_wrapper(self.permission, function)

或者我们可以使用functools.partial来完成整个工作:

def permission_check_wrapper(*args, function, permission, **kwargs):
   if not current_user.can(permission):
       abort(403)
    return function(*args, **kwargs)

def wrap_fn_with_permission_check(function, *, permission):
    return functools.update_wrapper(
        functools.partial(permission_check_wrapper,
                          function=function,
                          permission=permission),
        wrapped=function)

def permission_required(permission):
    return functools.partial(wrap_fn_with_permission_check,
                             permission=permission)

定义 @decorator(a,b,c) def foofoo = decorator(a,b,c)(foo) 的美妙之处在于语言不在乎你选择哪种实现技术。


3
具有参数的装饰器被简单地称为(具有该参数),以产生另一个装饰器。然后,通常情况下,将使用修饰后的函数作为其参数来调用该装饰器。因此,以下代码的翻译是:

@decorator(*args)
def bye():
    return "bye"

would be:

bye = decorator(*args)(bye)

或许你会觉得这样更清晰:
temp = decorator(*args)
bye = temp(bye)

除了当然没有实际创建temp变量之外,在您的第二个问题中,@admin_required被定义为@permission_required(Permission.ADMINISTER)的快捷方式。

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