装饰器设置功能的属性

时间:2013-04-03 17:48:36

标签: python attributes decorator python-decorators function-attributes

我希望只有登录用户具有所需的权限级别才能执行不同的功能。

为了让我的生活更复杂,我只想使用装饰器。下面我尝试在'装饰'函数上设置属性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'

我错过了什么?

3 个答案:

答案 0 :(得分:17)

您正在内部(包装器)函数中设置属性。您根本不需要包装函数

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

毕竟,您正在使用装饰器返回的包装器替换包装函数,因此这是您要查找属性的对象。

答案 1 :(得分:1)

你的装饰者应该返回一个可以替换do_xdo_y的函数,而不是返回do_xdo_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

答案 2 :(得分:0)

问题在于,即使您在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'