Как применить условный декоратор с аргументами? - PullRequest
0 голосов
/ 18 ноября 2018

У меня проблемы с выяснением этого, и я продолжаю получать 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

1 Ответ

0 голосов
/ 18 ноября 2018

Вы возвращаете outer, не вызывая его, в результате вызова представления . Таким образом, Flask должен обработать этот как ответ представления , а ответ, который не является строкой, кортежем или 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 / login`

Вот что происходит, когда вы получаете доступ к /login по HTTP:

  • Flask ищет функцию просмотра для /login, находит inner, вызывает ее
  • Тест if condition: неверен, перейдите к следующему разделу
  • decorator(1, 2) называется
    • def wrapped(func): ... выполняется, создавая внутреннюю функцию с foo и bar в замыкании
    • return wrapped возвращает звонящему
  • decorator(1, 2)... - это wrapped..., поэтому wrapped(func) называется
    • def outer(): ... выполняется, создает внутреннюю функцию с func в ее закрытии. Декоратор @wraps(func) присоединяет имя func к inner.
    • return outer возвращает функцию outer вызывающей стороне.
  • 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
...