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

sca*_*one 6 python python-3.x python-decorators

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

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

@decorator
def bye():
    return "bye"
Run Code Online (Sandbox Code Playgroud)

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

bye = decorator(bye)
Run Code Online (Sandbox Code Playgroud)

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

@decorator(*args)
def bye():
    return "bye"
Run Code Online (Sandbox Code Playgroud)

“无糖”版本怎么样?该函数是否作为参数之一传递到内部?

bye = decorator("argument", bye)
Run Code Online (Sandbox Code Playgroud)

第二个问题(与第一个问题相关,但更实际的例子);

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)
Run Code Online (Sandbox Code Playgroud)

这里,permission_required装饰器被传递给新创建的名为admin_required的装饰器的 return 语句。我不知道这是如何运作的。主要是 return 语句,我们返回原始装饰器+函数(以奇怪的语法)。有人可以详细说明一下吗?- 非常欢迎详细信息

zwo*_*wol 9

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

@decorator(a, b, c)
def function(): pass
Run Code Online (Sandbox Code Playgroud)

它是写作的语法糖

def function(): pass

function = decorator(a, b, c)(function)
Run Code Online (Sandbox Code Playgroud)

也就是说,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)
Run Code Online (Sandbox Code Playgroud)

当你使用装饰器时,例如

@permission_required(Permission.DESTRUCTIVE)
def erase_the_database():
    raise NotImplemented # TBD: should we even have this?
Run Code Online (Sandbox Code Playgroud)

您首先实例化该类,传递Permission.DESTRUCTIVE__init__,然后将该实例作为函数调用并作为参数erase_the_database,这将调用该__call__方法,该方法构造包装的函数并返回它。

这样想admin_required应该更容易理解:它是permission_required类的一个实例,还没有被调用。基本上是为了简写:

@admin_required
def add_user(...): ...
Run Code Online (Sandbox Code Playgroud)

而不是打字

@permission_required(Permission.ADMINISTER)
def add_user(...): ...
Run Code Online (Sandbox Code Playgroud)

现在,按照你的方式...

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
Run Code Online (Sandbox Code Playgroud)

实际上只是同一件事的另一种写法。wrap从隐式返回permission_required创建一个闭包对象。它可以像函数一样被调用,当你这样做时,它会调用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)
Run Code Online (Sandbox Code Playgroud)

或者我们可以用以下方法完成整个工作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)
Run Code Online (Sandbox Code Playgroud)

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