将两个 python 装饰器合二为一

Posted

技术标签:

【中文标题】将两个 python 装饰器合二为一【英文标题】:Combine two python decorators into one 【发布时间】:2013-06-09 21:32:55 【问题描述】:

这里有两个装饰器我想组合起来,因为它们非常相似,不同之处在于如何处理未经过身份验证的用户。我希望有一个可以用参数调用的装饰器。

# Authentication decorator for routes
# Will redirect to the login page if not authenticated
def requireAuthentication(fn):
    def decorator(**kwargs):
        # Is user logged on?
        if "user" in request.session:
            return fn(**kwargs)
        # No, redirect to login page
        else:
            redirect('/login?url=01'.format(request.path, ("?" + request.query_string if request.query_string else '')))
    return decorator

# Authentication decorator for routes
# Will return an error message (in JSON) if not authenticated
def requireAuthenticationJSON(fn):
    def decorator(**kwargs):
        # Is user logged on?
        if "user" in request.session:
            return fn(**kwargs)
        # No, return error
        else:
            return 
                "exception": "NotAuthorized",
                "error" : "You are not authorized, please log on"
            
    return decorator

目前我正在将这些装饰器用于特定路线,例如

@get('/day/')
@helpers.requireAuthentication
def day():
    ...

@get('/night/')
@helpers.requireAuthenticationJSON
def night():
    ...

我更喜欢这个:

@get('/day/')
@helpers.requireAuthentication()
def day():
    ...

@get('/night/')
@helpers.requireAuthentication(json = True)
def night():
    ...

我在 python 3.3 上使用 Bottle 框架。有可能做我想做的事吗?怎么样?

【问题讨论】:

【参考方案1】:

您可以为这两个装饰器创建包装器:

def requireAuthentication(json=False):
    if json:
        return helpers.requireAuthenticationJSON
    else:
        return helpers.requireAuthentication

或者

import functools
# Authentication decorator for routes
# Will redirect to the login page if not authenticated
def requireAuthentication(json=False):
    def requireAuthentication(fn):
        @functools.wraps(fn)
        def decorator(*args, **kwargs):
            # Is user logged on?
            if "user" in request.session:
                return fn(*args, **kwargs)
            if json:
                 return 
                "exception": "NotAuthorized",
                "error" : "You are not authorized, please log on"
            
            return redirect('/login?url=01'.format(request.path, 
                                                       ("?" + request.query_string if request.query_string else '')))
        return decorator
    return requireAuthentication

【讨论】:

【参考方案2】:

只需添加另一个包装器即可捕获json 参数:

def requireAuthentication(json=False):
    def decorator(fn):
        def wrapper(**kwargs):
            # Is user logged on?
            if "user" in request.session:
                return fn(**kwargs)

            # No, return error
            if json:
                return 
                    "exception": "NotAuthorized",
                    "error" : "You are not authorized, please log on"
                
            redirect('/login?url=01'.format(request.path, ("?" + request.query_string if request.query_string else '')))
        return wrapper
    return decorator

我已将您原来的 requireAuthentication 函数重命名为 decorator(因为这是该函数所做的,它装饰了 fn)并将旧的 decorator 重命名为 wrapper,这是通常的约定。

无论你把什么放在之后@ 都是一个表达式,首先求值以找到实际的装饰器函数。 @helpers.requireAuthentication() 表示您要调用 requireAuthentication 并且它的 返回值 然后用作 @ 行适用的函数的实际装饰器。

【讨论】:

以上是关于将两个 python 装饰器合二为一的主要内容,如果未能解决你的问题,请参考以下文章

python 多个装饰器的调用顺序

Python自学之乐-装饰器浅谈

python闭包与装饰器

python之“装饰器”

《Python学习之路 -- Python基础之装饰器》

python再议装饰器