@asyncio.coroutine против async def

С asyncio библиотекой, которую я видел,

@asyncio.coroutine
def function():
    ...

а также

async def function():
    ...

используется взаимозаменяемо.

Есть ли функциональная разница между этими двумя?

Ответ 1

Да, существуют функциональные различия между нативными сопрограммами, использующими синтаксис asyncio.coroutine async def и asyncio.coroutine основе генератора, используя декоратор asyncio.coroutine.

Согласно PEP 492, который вводит синтаксис async def:

  1. Собственные объекты coroutine не реализуют __iter__ и __next__. Следовательно, они не могут быть повторены или переданы в iter(), list(), tuple() и другие встроенные модули. Они также не могут использоваться в цикле for..in.

    Попытка использовать __iter__ или __next__ на нативном сопрограмме coroutine приведет к типу TypeError.

  2. Обычные генераторы не могут yield from native coroutines: это приведет к типу TypeError.

  3. основанные на генераторе сопрограммы (для асинхронного кода должны быть украшены @asyncio.coroutine) могут @asyncio.coroutine yield from собственных сопроводительных объектов.

  4. inspect.isgenerator() и inspect.isgeneratorfunction() возвращает False для собственных объектов coroutine и собственных функций coroutine.

Пункт 1 выше означает, что в то время как функции coroutine, определенные с помощью синтаксиса @asyncio.coroutine могут вести себя как традиционные функции генератора, те, которые определены с синтаксисом async def не могут.

Вот две минимальные, якобы эквивалентные функции сопрограммы, определенные с помощью двух синтаксисов:

import asyncio

@asyncio.coroutine
def decorated(x):
    yield from x 

async def native(x):
    await x 

Хотя байт-код для этих двух функций почти идентичен:

>>> import dis
>>> dis.dis(decorated)
  5           0 LOAD_FAST                0 (x)
              3 GET_YIELD_FROM_ITER
              4 LOAD_CONST               0 (None)
              7 YIELD_FROM
              8 POP_TOP
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(native)
  8           0 LOAD_FAST                0 (x)
              3 GET_AWAITABLE
              4 LOAD_CONST               0 (None)
              7 YIELD_FROM
              8 POP_TOP
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE

... с той лишь разницей, что GET_YIELD_FROM_ITER vs GET_AWAITABLE, они ведут себя совершенно по-разному, когда делается попытка перебора объектов, которые они возвращают:

>>> list(decorated('foo'))
['f', 'o', 'o']

>>> list(native('foo'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'coroutine' object is not iterable

Очевидно, что 'foo' не является ожидаемым, поэтому попытка вызвать native() с ним не имеет большого смысла, но, надеюсь, ясно, что возвращаемый объект coroutine не является итерируемым, независимо от его аргумента.

Более подробное исследование синтаксиса async/await Бретта Кэннона: Как работает асинхронный/ждущий процесс в Python 3.5? охватывает эту разницу более подробно.

Ответ 2

async def - новый синтаксис из Python 3.5. Вы можете использовать await, async with и async for внутри async def s.

@coroutine является функциональным аналогом для async def но он работает в Python 3. 4+ и использует yield from строительства вместо await.

Для практической перспективы просто никогда не используйте @coroutine если ваш Python равен 3. 5+.

Ответ 3

Из Python 3.5 coroutines формально стал отдельным типом и, следовательно, синтаксисом async def, а также await.

До этого Python 3.4 создавал сопрограммы, обертывая регулярные функции в generators, отсюда и синтаксис декоратора, и больше yield from генератора.