从另一个装饰器创建装饰器 (python)

creating decorator out of another decorator (python)

在 python 中的装饰器主题上花了几个小时后,我仍然有两个问题。

首先;如果你有没有参数的装饰器,sytntax 是这样的:

@decorator
def bye():
    return "bye"

这只是一个语法糖,与此相同

bye = decorator(bye)

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

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

"no-sugar" 版本怎么样?函数是否作为参数之一传入内部?

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[=32的return语句=].我不知道这是怎么回事。主要是 return 语句,其中我们 returning 原始装饰器 + 函数(使用奇怪的语法)。有人可以详细说明吗? - 非常欢迎提供详细信息

带有参数的装饰器被简单地调用(带有该参数),以产生另一个装饰器。然后像往常一样以装饰函数作为参数调用该装饰器。所以翻译:

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

将是:

bye = decorator(*args)(bye)

或者您可能会发现更清楚:

temp = decorator(*args)
bye = temp(bye)

(当然,实际上没有创建 temp 变量。)

在您的第二期中,@admin_required 被定义为 @permission_required(Permission.ADMINISTER) 的快捷方式。

当参数以修饰符号给出时,

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

写作的语法糖

def function(): pass

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

也就是说,decorator 使用参数 a、b、c 调用,然后 对象 returns 使用唯一参数调用 function.

当装饰器是 class 时,最容易理解它的意义。我将使用您的 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?

首先实例化 class,将 Permission.DESTRUCTIVE 传递给 __init__,然后将该实例作为函数调用,并以 erase_the_database 作为参数,调用__call__ 方法,构造包装函数和 returns 它。

这样想,admin_required应该更容易理解:它是permission_requiredclass的一个实例,还没有被调用。基本上是 shorthand:

@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 隐式地创建了一个 闭包对象 。它可以像函数一样被调用,当你调用它时调用 wrap。它会记住传递给 permission_requiredpermission 的值,以便 wrap 可以使用它。这正是我上面显示的 class 所做的。 (事实上​​ ,像 C++ 和 Rust 这样的编译语言通常通过将闭包脱糖到 class 定义中来实现闭包,就像我展示的那样。)

注意 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 foo 定义为脱糖到 foo = decorator(a,b,c)(foo) 的美妙之处在于,该语言不关心您选择这几种实现技术中的哪一种。