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

时间:2016-06-13 11:55:37

标签: python decorator python-decorators

我正在尝试在工作中集成一个非常旧的系统和一个较新的系统。我能做的最好的事情就是利用系统使用的RSS消防站类型的饲料。目标是使用此RSS源使其他系统在某些人执行操作时执行某些操作。

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

我当前的解决方案有很多看起来像这样的函数,这些函数根据Feed中的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

我想创建一个带有has_permissions的{​​{1}}装饰器,这样我就可以删除每个函数中的冗余user_id检查。

has_permissions

不幸的是,我不知道如何编写这样的装饰器。我看到的所有教程都有@has_permissions(user_id) def function1(): # Do this function 行,带有硬编码值,但在我的情况下,它需要在运行时传递,并且每次调用函数时都会有所不同。

如何实现此功能?

3 个答案:

答案 0 :(得分:6)

在你的问题中,你已经同时命名了user_id的检查,以及想要的装饰器has_permissions,所以我将举一个名称更清晰的例子:让我们来看看当颜色(字符串)为'green'时,创建一个调用底层(装饰)函数的装饰器。

Python装饰器是函数工厂

装饰器本身(以下例子中的if_green)是一个函数。它需要一个函数作为参数进行修饰(在我的示例中名为function)并返回一个函数(示例中为run_function_if_green)。通常,返回的函数在某个时刻调用传递的函数,从而使用它之前或之后可能运行的其他操作“装饰”它,或者两者兼而有之。

当然,它可能只是有条件地运行它,因为你似乎需要:

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正如您在上面的代码中看到的那样)。就其本质而言,它返回不同的功能。 Python然后将原始函数替换为装饰器返回的

因此,在后续调用中,返回的函数(run_function_if_green,原始print_if_greenfunction)被调用为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不转发它)。由于您可以自由地实现装饰器返回的函数,它还可以使用更少,更多或不同的参数调用原始函数,在将它们传递给原始函数之前对它们执行任何所需的转换,或者执行其他疯狂的操作。 / p>

概念,概念,概念

理解装饰器需要知识和理解Python语言的各种其他概念。 (其中大多数并非特定于Python,但人们可能仍然不了解它们。)

为了简洁起见(这个答案足够长),我已经跳过或掩盖了大部分内容。对于更全面的快速运行(我认为)所有相关的快速运行,请参考例如Understanding Python Decorators in 12 Easy Steps!

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

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

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

现在,当从字典中提取动作时,使用装饰器将其包装起来以创建一个执行自动权限检查的新callable:

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

现在,当您致电checked_action时,它将首先检查与user_id相对应的权限,然后再执行基础操作。