简体   繁体   English

装饰器来设置函数的属性

[英]decorator to set attributes of function

I want different functions to be executable only if the logged in user has the required permission level.我希望只有登录用户具有所需的权限级别才能执行不同的功能。

To make my life more simple I want to use decorators.为了让我的生活更简单,我想使用装饰器。 Below I attempt to set attribute permission on 'decorated' functions - as shown below.下面我尝试为“装饰”功能设置属性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):
    ...

But when I do:但是当我这样做时:

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

I get an error 'function' object has no attribute 'permission_required'我收到一个错误'function' object has no attribute 'permission_required'

What am I missing?我错过了什么?

You are checking the attribute on the inner (wrapper) function, but set it on the original (wrapped) function.您正在检查内部(包装器)函数上的属性,但将其设置在原始(包装器)函数上。 But you need a wrapper function at all :但是您根本需要一个包装函数:

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

Your decorator needs to return something that'll replace the original function.您的装饰器需要返回一些可以替换原始函数的东西 The original function itself (with the attribute added) will do fine for that, because all you wanted to do is add an attribute to it.原始函数本身(添加了属性)可以很好地做到这一点,因为您要做的就是向它添加一个属性。

If you still need a wrapper, then set the attribute on the wrapper function instead:如果你仍然需要一个包装器,那么在包装器函数上设置属性:

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

After all, you are replacing the wrapped function with the wrapper returned by the decorator, so that's the object you'll be looking for the attribute on.毕竟,您正在用装饰器返回的包装器替换被包装的函数,因此这就是您要在其上查找属性的对象。

I also added the @functools.wraps() decorator to the wrapper, which copied across important identifying information and other helpful things from func to the wrapper, making it much easier to work with.我还在包装器中添加了@functools.wraps()装饰器,它将重要的识别信息和其他有用的东西从func复制到包装器,使其更容易使用。

Your decorator should return a function that can replace do_x or do_y , not return the execution result of do_x or do_y You can modity you decorate as below:你的装饰应返回功能,可以取代do_xdo_y ,不返回执行结果do_xdo_y可以modity你装饰如下:

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

Of course, you have another brief solution:当然,您还有另一个简短的解决方案:

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

The problem is that, even though you are setting the desired property to the wrapped function in inner , inner is returning whatever is returned by the decorated function, which usually never is the function itself.问题是,即使您将所需的属性设置为inner的包装函数, inner也会返回装饰函数返回的任何内容,通常永远不会是函数本身。

You should just return the very same original function with the attribute added, thus you do not really want to worry about what arguments this original decorated function might take, meaning you can get rid of one of the wrapping levels:您应该只返回添加了属性的完全相同的原始函数,因此您真的不想担心这个原始装饰函数可能采用哪些参数,这意味着您可以摆脱包装级别之一:

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

This works just fine:这工作得很好:

>>> do_x
<function __main__.do_x(arg1, arg2)>
>>> do_x.permission_required
'user'
>>> do_y.permission_required
'admin'

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM