2016-11-13 2 views

ответ

29

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

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

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

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

  2. Простые генераторы не yield fromродной сопрограммы: выполнение так приведет к TypeError.

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

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

пункт 1 выше означает, что в то время как сопрограмма функции, определяемые с помощью синтаксиса @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' не является awaitable, поэтому попытка вызвать native() с ним не имеет смысла, но суть, надеюсь, понятно, что coroutine объект возвращает не итерацию, независимо от его аргумента.

Более подробное исследование синтаксиса async/await от Brett Cannon: How the heck does async/await work in Python 3.5? охватывает эту разницу более подробно.

10

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+.

+0

Было бы полезно знать, почему именно «никогда не использовать @coroutine» в 3.5+. Есть ли настоящая причина или это мнение/эмпирическое правило? Например, у меня есть некоторые декораторы @coroutine в 3.4-унаследованной кодовой базе, но вся новая разработка находится в 3.5. Должен ли я превращать эти декораторы в 'async def'? – hmijail

+0

Чтобы приостановить выполнение сопрограммы, необходимо «уступить». Но 'yield' не допускается изнутри собственных сопрограмм! Так выглядит, что по-прежнему необходимо использовать '@ coroutine' даже в Python 3.5+ – lesnik

+0

№. Просто используйте' await asyncio.sleep (0) '. –

3

С Python 3.5coroutines официально стал особым типом и, таким образом, синтаксис async def, наряду с await отчетности.

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

Смежные вопросы