Python asyncio, фьючерсы и доходность

Рассмотрим следующую программу (работает на CPython 3.4.0b1):

import math import asyncio from asyncio import coroutine @coroutine def fast_sqrt(x): future = asyncio.Future() if x >= 0: future.set_result(math.sqrt(x)) else: future.set_exception(Exception("negative number")) return future def slow_sqrt(x): yield from asyncio.sleep(1) future = asyncio.Future() if x >= 0: future.set_result(math.sqrt(x)) else: future.set_exception(Exception("negative number")) return future @coroutine def run_test(): for x in [2, -2]: for f in [fast_sqrt, slow_sqrt]: try: future = yield from f(x) print("\n{} {}".format(future, type(future))) res = future.result() print("{} result: {}".format(f, res)) except Exception as e: print("{} exception: {}".format(f, e)) loop = asyncio.get_event_loop() loop.run_until_complete(run_test()) 

У меня есть 2 (связанных) вопроса:

  1. Даже с декоратором на fast_sqrt , Python, похоже, полностью оптимизирует будущее, созданное в fast_sqrt , и возвращается обычный float . Которая затем взрывается в run_test() с yield from

  2. Почему мне нужно оценивать future.result() в run_test чтобы получить значение огненного исключения? Документы говорят, что yield from <future> "приостанавливает сопрограмму до тех пор, пока не будет сделано будущее, а затем вернет результат в будущем или создаст исключение". Почему мне нужно вручную восстановить результат в будущем?

Вот что я получаю:

 oberstet@COREI7 ~/scm/tavendo/infrequent/scratchbox/python/asyncio (master) $ python3 -V Python 3.4.0b1 oberstet@COREI7 ~/scm/tavendo/infrequent/scratchbox/python/asyncio (master) $ python3 test3.py 1.4142135623730951 <class 'float'> <function fast_sqrt at 0x00B889C0> exception: 'float' object has no attribute 'result' Future<result=1.4142135623730951> <class 'asyncio.futures.Future'> <function slow_sqrt at 0x02AC8810> result: 1.4142135623730951 <function fast_sqrt at 0x00B889C0> exception: negative number Future<exception=Exception('negative number',)> <class 'asyncio.futures.Future'> <function slow_sqrt at 0x02AC8810> exception: negative number oberstet@COREI7 ~/scm/tavendo/infrequent/scratchbox/python/asyncio (master) 

Хорошо, я нашел «проблему». yield from asyncio.sleep в slow_sqrt автоматически сделает его сопрограммой. Ожидание должно быть сделано по-другому:

 def slow_sqrt(x): loop = asyncio.get_event_loop() future = asyncio.Future() def doit(): if x >= 0: future.set_result(math.sqrt(x)) else: future.set_exception(Exception("negative number")) loop.call_later(1, doit) return future 

Все 4 варианта здесь .

    Что касается №1: Python этого не делает. Обратите внимание, что функция fast_sqrt вы написали (т.е. перед любыми декораторами), не является функцией генератора, функцией сопрограммы, заданием или тем, что вы хотите назвать. Это обычная функция, выполняемая синхронно и возвращающая то, что вы пишете после оператора return . В зависимости от наличия @coroutine самые разные вещи. Это просто невезение, что оба результата та же ошибка.

    1. Без декоратора fast_sqrt(x) работает как обычная функция и возвращает будущее float ( независимо от контекста ). Это будущее потребляется future = yield from ... , оставляя в future float (который не имеет метода result ).

    2. С декоратором вызов f(x) проходит через функцию обертки, созданную @coroutine . Эта функция- fast_sqrt вызывает fast_sqrt и распаковывает полученное будущее для вас, используя yield from <future> . Следовательно, эта функция-оболочка сама является сопрограммой. Поэтому future = yield from ... ждет на этой сопрограмме и снова оставляет плавание.

    Что касается № 2, yield from <future> работает (как описано выше, вы используете его при использовании fast_sqrt ), и вы также можете написать:

     future = yield from coro_returning_a_future(x) res = yield from future 

    (Modulo, что он не работает для fast_sqrt как написано, и не fast_sqrt вам дополнительной асинхронности, потому что будущее уже сделано к тому времени, когда оно возвращается из coro_returning_a_future .)

    Ваша основная проблема заключается в том, что вы путаете сопрограммы и фьючерсы. Обе реализации sqrt пытаются выполнить асинхронные задачи, приводящие к фьючерсам. Из моего ограниченного опыта это не так, как обычно пишется код асинцио. Это позволяет вам тянуть как строительство будущего, так и вычисления, которые будущее означает для двух независимых задач async. Но вы этого не делаете (вы возвращаете уже готовое будущее). И большую часть времени это не очень полезная концепция. Если вам нужно выполнить какое-то вычисление асинхронно, вы либо записываете его как сопрограмму (которая может быть приостановлена), либо вы вставляете ее в другой поток и обмениваетесь ею, используя yield from <future> . Не оба.

    Чтобы сделать вычисление с квадратным корнем асинхронным, просто напишите регулярную сопрограмму, выполняющую вычисление, и return результат ( fast_sqrt coroutine превратит fast_sqrt в задачу, выполняющуюся асинхронно и ее можно ждать).

     @coroutine def fast_sqrt(x): if x >= 0: return math.sqrt(x) else: raise Exception("negative number") @coroutine # for documentation, not strictly necessary def slow_sqrt(x): yield from asyncio.sleep(1) if x >= 0: return math.sqrt(x) else: raise Exception("negative number") ... res = yield from f(x) assert isinstance(res, float)