【问题标题】:How to apply conditional decorator with arguments?如何应用带参数的条件装饰器?
【发布时间】:2019-04-20 17:10:02
【问题描述】:

我无法弄清楚这一点,并且不断收到 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 解决了TypeError,但出现了一个新的:

TypeError: 'function' object is not iterable

【问题讨论】:

  • return wrapperconditional() 中的def inner() 有什么关系?应该是return inner
  • @MartijnPieters 是的,错别字已修复。
  • 条件应该应用一次、导入时还是每次调用URL视图函数?
  • @MartijnPieters 每次调用 URL 视图时。
  • 如果条件是True,条件的目标是根本不应用decorator(1, 2)吗?

标签: python flask decorator


【解决方案1】:

您正在返回 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 innerinner 返回给调用者
  • login = inner 被设置为 `@conditional
  • 的结果
  • @app.route()registersinneras the route handler for/login`

当您通过 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 返回给调用者
  • Flask 被给予 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

【讨论】:

  • 另外,我不确定我知道*args, **kwargs 的来源。
  • @pstatix: 如果你想在 any 视图函数上使用@conditional,那么你必须处理带有路由参数的视图,比如@987654372 @ 其中bar_id 然后作为参数传递给视图函数。然后定义函数类似于def foo_bar(bar_id): ...inner() 函数替换了foo_bar,通过给它*args, **kwargs,它将接受任意数量的参数,然后通过(*args, **kwargs) 调用将这些参数传递给包装函数。
猜你喜欢
  • 2018-01-07
  • 2022-12-28
  • 2014-07-21
  • 2020-09-05
  • 1970-01-01
  • 2022-08-15
  • 2014-10-14
  • 2012-03-03
相关资源
最近更新 更多