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

问题描述 投票:0回答:2

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

第一;如果你有没有参数的装饰器,那么 sytntax 是这样的:

@decorator
def bye():
    return "bye"

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

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 的装饰器的返回语句。我不知道这是如何运作的。主要是 return 语句,我们返回原始装饰器+函数(以奇怪的语法)。有人可以详细说明一下吗? - 非常欢迎详细信息

python python-3.x python-decorators
2个回答
10
投票

当参数以装饰器表示法给出时,

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

它是写作的语法糖

def function(): pass

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

也就是说,使用参数 a、b、c 调用

decorator
,然后使用唯一参数 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 NotImplementedError # 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
实际上只是同一件事的另一种写法。从 

wrap

 返回 
permission_required
 隐式创建一个 
closure 对象。它可以像函数一样被调用,当你这样做时,它会调用 wrap
。它会记住传递给 
permission
permission_required
 的值,以便 
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 foo

 定义为脱糖到 
foo = decorator(a,b,c)(foo)
 的美妙之处在于,该语言并不关心您选择这几种实现技术中的哪一种。


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

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

将是:

bye = decorator(*args)(bye)

或者也许你会发现更清楚:

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

(当然,除了实际上没有创建

temp

 变量。)

在第二期中,

@admin_required

 被定义为 
@permission_required(Permission.ADMINISTER)
 的快捷方式。

© www.soinside.com 2019 - 2024. All rights reserved.