如何将条件修饰符与参数一起使用?

时间:2018-11-17 22:15:49

标签: python flask decorator

我在弄清楚这一点时遇到了麻烦,而且我一直遇到TypeError。我需要一个装饰器来应用另一个仅在条件成立时才接受参数的装饰器。 TypeError与传递给outer()方法的参数有关。

def decorator(foo, bar):
    def wrapped(func):
        @wraps(func)
        def outer():
            ...stuff with foo and bar...
            return func()
        return outer
    return wrapped


def conditional(func):
    @wraps(func)
    def inner():
        if some_condition:
            raise Error
        return decorator(1, 2)(func)
    return inner

@app.route('/login', methods=['POST'])
@conditional
def login():
    ...

这会生成TypeError: outer() takes 0 positional arguments but 2 were given,但是通过一些基本的打印语句(主要是outer(*args), print(args)),我发现它是以下内容:

第一个位置:

{'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=964>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x0000014341FCA0D0>, 'SERVER_SOFTWARE': 'Werkzeug/0.14.1', 'REQUEST_METHOD': 'POST', 'SCRIPT_NAME': '', 'PATH_INFO': '/login', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 54900, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': '127.0.0.1:5000', 'HTTP_USER_AGENT': 'python-requests/2.20.1', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', 'HTTP_ACCEPT': '*/*', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_AUTHORIZATION': 'Basic cGF1bDpmb29iYXI=', 'CONTENT_LENGTH': '0', 'werkzeug.request': <Request 'http://127.0.0.1:5000/login' [POST]>}

第二位置:

<function run_wsgi_app.<locals>.start_response at 0x0000014341FCA378>

我的语法错误出现在某个地方,不确定在哪里。

使用*args可以解决TypeErrror,但是会出现一个新的:

TypeError: 'function' object is not iterable

1 个答案:

答案 0 :(得分:3)

作为视图调用的结果,您将返回outer,而没有调用 。因此Flask必须treat this as a view response,并且不是字符串,元组或Response的响应被视为WSGI对象。 WSGI响应的正常处理方式是将其称为<wsgi response>(environment, start_response)

您需要返回调用outer()的实际结果。

这是在模块导入时发生的情况:

  • def login(): ...被执行,创建了一个功能对象login
  • @conditional用作login的装饰器。
    • def inner(): ...被执行,并在其闭包中使用func创建一个嵌套函数。 @wraps(func)装饰器将func的名称附加到inner
    • return inner向呼叫者返回inner
  • login = inner被设置为“ @conditional
  • ”的结果
  • @ app.route()registers内部as the route handler for /登录

当您通过HTTP访问/login时,会发生以下情况:

  • Flask查找/login的视图函数,找到inner,将其调用
  • if condition:测试为假,请跳至下一部分
  • decorator(1, 2)被称为
    • def wrapped(func): ...被执行,并在闭包中使用foobar创建一个内部函数
    • return wrapped返回到呼叫者
  • decorator(1, 2)...wrapped...,因此wrapped(func)被称为
    • def outer(): ...被执行,创建一个内部函数,其内部带有func@wraps(func)装饰器将func的名称附加到inner
    • return outerouter函数返回给调用者。
  • outer返回给呼叫者
  • 给烧瓶outer作为响应,该响应被视为WSGI对象。

您在这里错过了最后一次通话:

def conditional(func):
    @wraps(func)
    def inner():
        if some_condition:
            raise Error
        return decorator(1, 2)(func)()  # call the decorated `func()`
    return inner

但是,除非希望防止阻止应用decorator(1, 2)调用的条件,否则您要存储decorator(1, 2)(func)结果,而不是为每个调用修饰它:

def conditional(func):
    func = decorator(1, 2)(func)
    @wraps(func)
    def inner():
        if some_condition:
            raise Error
        return func()
    return inner

一个中间原因可能是只调用一次decorator(1, 2),一次创建实际的装饰器函数:

def conditional(func):
    dec = decorator(1, 2)
    @wraps(func)
    def inner():
        if some_condition:
            raise Error
        return dec(func)()
    return inner

最后,考虑将传递给inner()的参数传递给装饰后的视图函数,因此可以在接受路由参数的视图函数上使用@condition

def conditional(func):
    @wraps(func)
    def inner(*args, **kwargs):
        if some_condition:
            raise Error
        return decorator(1, 2)(func)(*args, **kwargs)
    return inner