我希望不同的函数只有在登录用户具有所需的权限级别时才能执行。
为了让我的生活更简单,我想使用装饰器。下面我尝试在'装饰'函数上设置属性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'
我错过了什么?
您正在检查内部(包装)函数上的属性,但将其设置在原始(包装)函数上。但是你需要一个包装器函数:
def permission(permission_required):
def decorator(func):
func.permission_required = permission_required
return func
return decorator
你的装饰器需要返回一些将取代原来的函数。原来的函数本身(添加了属性)可以很好地做到这一点,因为您所要做的就是为它添加一个属性。
如果您仍然需要一个包装器,那么在包装器函数上设置该属性:
from functools import wraps
def permission(permission_required):
def decorator(func):
@wraps(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
毕竟,您正在用装饰器返回的包装器替换包装函数,因此这就是您将在其上查找属性的对象。
我还向包装器添加了@functools.wraps()
装饰器,它将重要的标识信息和其他有用的东西从func
复制到包装器,使其更容易使用。
您的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'