python 是否允许我在运行时将动态变量传递给装饰器?

Does python allow me to pass dynamic variables to a decorator at runtime?

我正在尝试集成一个非常旧的系统和一个更新的系统。我能做的最好的事情就是使用系统使用的 RSS firehouse 类型的提要。目标是使用此 RSS 提要使其他系统在某些人做某事时执行某些操作。

我的想法是在某些功能周围包装一个装饰器,以检查用户(RSS 提要中提供的用户 ID)在新系统中是否具有权限。

我当前的解决方案有很多看起来像这样的函数,它们是根据提要中的 action 字段调用的:

actions_dict = {
    ...
    'action1': function1
}

actions_dict[RSSFEED['action_taken']](RSSFEED['user_id'])

def function1(user_id):
    if has_permissions(user_id):
         # Do this function

我想创建一个带有 user_idhas_permissions 装饰器,这样我就可以在我的每个函数中删除这个多余的 has_permissions 检查。

@has_permissions(user_id)
def function1():
    # Do this function

不幸的是,我不确定如何编写这样的装饰器。我看到的所有教程都有带有硬编码值的 @has_permissions() 行,但在我的例子中,它需要在运行时传递,并且每次调用函数时都会不同。

如何实现此功能?

简短的回答是否定的:你不能将动态参数传递给装饰器

但是...您当然可以通过编程方式调用它们:

首先让我们创建一个可以在执行函数之前执行权限检查的装饰器:

import functools

def check_permissions(user_id):
    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            if has_permissions(user_id):
                return f(*args, **kw)
            else:
                # what do you want to do if there aren't permissions?
                ...
        
         return wrapper

    return decorator

现在,当从字典中提取 action 时,使用装饰器包装它以创建一个新的可执行自动权限检查的可调用对象:

checked_action = check_permissions(RSSFEED['user_id'])(
    actions_dict[RSSFEED['action_taken']])

现在,当您调用 checked_action 时,它会在执行基础操作之前先检查与 user_id 对应的权限。

装饰器的输入(参数、包装函数)在 python 中相当静态。没有办法像您要求的那样动态传递参数。但是,如果可以在装饰器函数内的运行时从某处提取用户 ID,则可以实现您想要的..

例如在 Django 中,像 @login_required 这样的东西期望它们包装的函数有 request 作为第一个参数,而 Request 对象有一个 user他们可以利用的属性。另一个更丑陋的选择是拥有某种全局对象,您可以从中获取当前用户(参见 thread local storage)。

在你的问题中,你已经命名了 user_id 的检查以及想要的装饰器 has_permissions,所以我将举一个名称更清晰的例子: 让我们制作一个装饰器,当颜色(字符串)为 'green'.

时调用底层(装饰)函数

Python 装饰器是函数工厂

装饰器本身(if_green 在我下面的示例中)是一个函数。它需要一个函数作为参数(在我的示例中命名为 function)和 returns 一个函数(在示例中命名为 run_function_if_green)。通常,返回的函数会在某个时候调用传递的函数,因此 "decorating" 它可能会在它之前或之后或两者之间 运行 执行其他操作。

当然可能只是有条件的运行吧,你好像需要:

def if_green(function):
    def run_function_if_green(color, *args, **kwargs):
        if color == 'green':
            return function(*args, **kwargs)
    return run_function_if_green


@if_green
def print_if_green():
    print('what a nice color!')


print_if_green('red')  # nothing happens
print_if_green('green')  # => what a nice color!

当你用装饰器装饰一个函数时(就像我在此处使用 print_if_green 所做的那样)会发生什么,即装饰器(函数工厂,在我的示例中为 if_green)被调用原始函数(print_if_green 如您在上面的代码中所见)。就其性质而言,它 returns 具有不同的功能。 Python 然后 用装饰器返回的函数替换 原始函数。

所以在随后的调用中,它是返回的函数(run_function_if_green,原始的 print_if_green 作为 function)被调用为 print_if_green 并且有条件地进一步调用到原来的 print_if_green.

函数工厂可以生成带参数的函数

对装饰器的调用 (if_green) 只对每个装饰函数调用一次,而不是每次调用装饰函数时。但是,由于装饰器 返回 的函数一次永久 替换了 原始函数,因此每次调用原始函数时都会调用它而不是原始函数被调用。如果我们允许,它可以接受参数。

我给了它一个参数color,它用它自己来决定是否调用装饰函数。此外,我给了它惯用的 vararg 参数,它用来调用包装函数(如果它调用它),这样我就可以装饰带有任意数量的位置和关键字参数的函数:

@if_green                     
def exclaim_if_green(exclamation):
    print(exclamation, 'that IS a nice color!')

exclaim_if_green('red', 'Yay')  # again, nothing
exclaim_if_green('green', 'Wow')  # => Wow that IS a nice color!

if_green 装饰函数的结果是新的第一个参数被添加到它的签名前,这对原始函数来说是不可见的(因为 run_function_if_green 不转发它) .由于您可以自由地实现装饰器返回的函数,它还可以使用更少、更多或不同的参数调用原始函数,在将它们传递给原始函数之前对它们进行任何必要的转换或做其他疯狂的事情。

概念,概念,概念

理解装饰器需要了解和理解 Python 语言的各种其他概念。 (其中大部分都不是 Python 特有的,但人们可能仍然不知道它们。)

为了简洁起见(这个答案已经够长了),我跳过或忽略了其中的大部分内容。要通过(我认为)所有相关的更全面的速度运行,请咨询例如Understanding Python Decorators in 12 Easy Steps!.