装饰器用于设置函数属性

19

如果已登录用户具备所需的权限级别,则只能执行不同功能。

为了让我的生活更简单,我想使用装饰器。下面我尝试在“装饰”的函数上设置属性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'。我缺少了什么?

1
补充一句:我很确定你想要在这里使用functools.wraps。不是直接解决你的问题,而是因为当每个函数最终都被命名为“inner”,采用(*args, **kwargs),检查错误源时会变得异常困难,所以使用它将可以避免这种情况。 - abarnert
3个回答

28

你正在检查内部(包装)函数的属性,但将其设置在原始(被包装)函数上。 但你需要一个完整的包装函数:

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复制了重要的标识信息和其他有用的东西到包装器中,使得使用它更加容易。


我需要包装器允许在装饰函数中使用参数。我使用了你的代码,它很好用 - 但是当我为参数添加了包装器后,错误又出现了。 - Rich Tier
1
@rikAtee:你不需要一个包装器来允许在被装饰的函数中使用参数。第一个例子只是修改并返回该函数;它仍然接受与被装饰之前完全相同的参数。 - abarnert
@rikAtee:确实,如果你只是设置属性,则不需要包装器。只有在需要包装器的情况下才添加包装器(例如,添加额外的代码以操作参数或返回值,或在调用函数时执行额外的操作)。 - Martijn Pieters
1
不要忘记使用 functools 中的 @wraps 装饰器来装饰 wrapper,这样被装饰的函数将保留其属性(包括 __doc__)! - minmaxavg

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

1
你的内部函数什么也不做,因此你用一个仅设置包装函数属性的函数替换了原始函数,使其变得毫无用处。 - Martijn Pieters

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接