Дополнительный синхронный интерфейс для асинхронных функций

#python #asynchronous #tornado #synchronous

#python #асинхронный #торнадо #синхронный

Вопрос:

Я пишу библиотеку, которая использует Tornado Web tornado.httpclient.AsyncHTTPClient для выполнения запросов, что дает моему коду async интерфейс:

 async def my_library_function():
    return await ...
  

Я хочу сделать этот интерфейс необязательно последовательным, если пользователь предоставляет kwarg — что-то вроде: serial=True . Хотя вы, очевидно, не можете вызвать функцию, определенную с async помощью ключевого слова, из обычной функции без await . Это было бы идеально, хотя на данный момент почти наверняка невозможно на языке:

 async def here_we_go():
    result = await my_library_function()
    result = my_library_function(serial=True)
  

Я не смог найти ничего в Интернете, где кто-то придумал хорошее решение для этого. Я не хочу переопределять в основном один и тот же код без awaits разбрызгивания по всему.

Это то, что можно решить, или для этого потребуется поддержка со стороны языка?


Решение (хотя вместо этого используйте Jesse’s — объяснено ниже)

Приведенное ниже решение Джесси — это в значительной степени то, с чем я собираюсь пойти. В итоге я получил интерфейс, который изначально хотел, используя декоратор. Что-то вроде этого:

 import asyncio
from functools import wraps


def serializable(f):
    @wraps(f)
    def wrapper(*args, asynchronous=False, **kwargs):
        if asynchronous:
            return f(*args, **kwargs)
        else:
            # Get pythons current execution thread and use that
            loop = asyncio.get_event_loop()
            return loop.run_until_complete(f(*args, **kwargs))
    return wrapper
  

Это дает вам этот интерфейс:

 result = await my_library_function(asynchronous=True)
result = my_library_function(asynchronous=False)
  

Я проверил это в списке асинхронной рассылки python, и мне повезло, что Гвидо ответил, и он вежливо отклонил его по этой причине:

Запах кода — возможность вызывать одну и ту же функцию как асинхронно, так и синхронно вызывает большое удивление. Также это нарушает эмпирическое правило, согласно которому значение аргумента не должно влиять на возвращаемый тип.

Приятно знать, что это возможно, хотя и не считается отличным интерфейсом. Гвидо, по сути, предложил ответ Джесси и представил функцию переноса в качестве вспомогательной утилиты в библиотеке вместо того, чтобы скрывать ее в декораторе.

Комментарии:

1. возможно, написать код как сопрограмму на основе генератора, а затем использовать types.coroutine() для создания асинхронного эквивалента?

Ответ №1:

Если вы хотите вызвать такую функцию синхронно, используйте run_until_complete:

 asyncio.get_event_loop().run_until_complete(here_we_go())
  

Конечно, если вы часто делаете это в своем коде, вам следует придумать сокращение для этого оператора, возможно, просто:

 def sync(fn, *args, **kwargs):
    return asyncio.get_event_loop().run_until_complete(fn(*args, **kwargs))
  

Тогда вы могли бы сделать:

 result = sync(here_we_go)