Почему я получаю разные результаты при использовании понимания списка с сопрограммами с asyncio?

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

import asyncio

@asyncio.coroutine
def coro():
    return "foo"


# Writing the code without a list comp works,
# even with an asyncio.sleep(0.1).
@asyncio.coroutine
def good():
    yield from asyncio.sleep(0.1)
    result = []
    for i in range(3):
        current = yield from coro()
        result.append(current)
    return result


# Using a list comp without an async.sleep(0.1)
# works.
@asyncio.coroutine
def still_good():
    return [(yield from coro()) for i in range(3)]


# Using a list comp along with an asyncio.sleep(0.1)
# does _not_ work.
@asyncio.coroutine
def huh():
    yield from asyncio.sleep(0.1)
    return [(yield from coro()) for i in range(3)]


loop = asyncio.get_event_loop()
print(loop.run_until_complete(good()))
print(loop.run_until_complete(still_good()))
print(loop.run_until_complete(huh()))

Если я запустил этот код, я получаю этот вывод:

$ python3.4 /tmp/test.py
['foo', 'foo', 'foo']
['foo', 'foo', 'foo']
<generator object <listcomp> at 0x104eb1360>

Почему я получаю разные результаты для третьей функции huh()?

Ответы

Ответ 1

Исправить вашу проблему было бы поместить next(...) вместо ... в возврат третьей функции или лучше написать return list((yield from coro()) for i in range(3)) (кредиты для @zch для этой идеи) или даже лучше остаться с первая функция.


Дело в том, что вторая функция не является генератором. Это просто обычная функция, которая возвращает генератор понимания.

Например, этот код действителен вне генератора:

values = [(yield x) for x in range(3)]

Затем вы можете сделать это:

next(values)
0
next(values)
1
next(values)
2
next(values)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: [None, None, None]

Decorator @coroutine затем делает вторую функцию генератором путем итерации по результату, см. здесь, строка 143.

По контрасту первая и третья функции на самом деле являются генераторами, а декоратор @coroutine просто возвращает себя, см. здесь, строки 136-137. В первом случае генератор возвращает список (актуальный рейз StopIteration(['foo', 'foo', 'foo'])). В третьем случае он возвращает генератор понимания.