Декоратор для установки атрибутов функции
Я хочу, чтобы различные функции выполнялись только в том случае, если вошедший в систему пользователь имеет требуемый уровень разрешений.
Чтобы сделать мою жизнь сложнее, просто я хочу использовать декораторы. Ниже я пытаюсь установить атрибут 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'