Декоратор для установки атрибутов функции

Я хочу, чтобы различные функции выполнялись только в том случае, если вошедший в систему пользователь имеет требуемый уровень разрешений.

Чтобы сделать мою жизнь сложнее, просто я хочу использовать декораторы. Ниже я пытаюсь установить атрибут permission на "украшенные" функции - как показано ниже.

def permission(permission_required):
    def wrapper(func):
        def inner(*args, **kwargs):
            setattr(func, 'permission_required', permission_required)
            return func(*args, **kwargs)
        return inner
    return wrapper

@permission('user')
def do_x(arg1, arg2):

    ...

@permission('admin')
def do_y(arg1, arg2):
    ...

Но, когда я делаю:

fn = do_x
if logged_in_user.access_level == fn.permission_required:
    ...

Я получаю ошибку 'function' object has no attribute 'permission_required'

Что мне не хватает?

2 ответа

Решение

Вы устанавливаете атрибут во внутренней функции (обертке). Вам вообще не нужна функция-обёртка:

def permission(permission_required):
    def decorator(func):
        func.permission_required = permission_required
        return func
    return decorator

Ваш декоратор должен вернутьчто-то, что заменит оригинальную функцию. Сама оригинальная функция (с добавленным атрибутом) подойдет для этого, потому что все, что вы хотели сделать, это добавить к ней атрибут.

Если вам по-прежнему нужна оболочка, установите вместонее атрибут функции оболочки:

def permission(permission_required):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # only use a wrapper if you need extra code to be run here
            return func(*args, **kwargs)
        wrapper.permission_required = permission_required
        return wrapper
    return decorator

В конце концов, вы заменяете упакованную функцию оболочкой, возвращаемой декоратором, так что это объект, для которого вы будете искать атрибут.

Ваш декоратор должен вернуть функцию, которая может заменить do_x или же do_y, не вернуть результат выполнения do_x или же do_y Вы можете модернизировать, как вы украшаете, как показано ниже:

def permission(permission_required):
    def wrapper(func):
        def inner():
            setattr(func, 'permission_required', permission_required)
            return func
        return inner()
    return wrapper

Конечно, у вас есть другое краткое решение:

def permission(permission_required):
    def wrapper(func):
        setattr(func, 'permission_required', permission_required)
        return func
    return wrapper

Проблема в том, что, даже если вы устанавливаете желаемое свойство для обернутой функции в inner, inner возвращает то, что возвращает декорированная функция, что обычно никогда не является самой функцией.

Вы должны просто вернуть ту же самую исходную функцию с добавленным атрибутом, поэтому вам не нужно беспокоиться о том, какие аргументы может принимать эта оригинальная декорированная функция, то есть вы можете избавиться от одного из уровней упаковки:

def permission(permission_required):
   def wrapper(func):
       setattr(func, 'permission_required', permission_required)
       return func
   return wrapper

@permission('user')
def do_x(arg1, arg2):
    pass

@permission('admin')
def do_y(arg1, arg2):
    pass

Это прекрасно работает:

>>> do_x
<function __main__.do_x(arg1, arg2)>
>>> do_x.permission_required
'user'
>>> do_y.permission_required
'admin'
Другие вопросы по тегам