Yes, there are functional differences between native coroutines using async def
syntax and generator-based coroutines using the asyncio.coroutine
decorator.
According to PEP 492, which introduces the async def
syntax:
Native coroutine objects do not implement __iter__
and
__next__
methods. Therefore, they cannot be iterated over or passed
to iter()
, list()
, tuple()
and other built-ins. They also
cannot be used in a for..in
loop.
An attempt to use __iter__
or __next__
on a native coroutine
object will result in a TypeError .
Plain generators cannot yield from
native coroutines: doing so
will result in a TypeError .
generator-based coroutines (for asyncio code must be decorated with
@asyncio.coroutine
) can yield from
native coroutine objects.
inspect.isgenerator()
and inspect.isgeneratorfunction()
return False
for native coroutine objects and native coroutine functions.
Point 1 above means that while coroutine functions defined using the @asyncio.coroutine
decorator syntax can behave as traditional generator functions, those defined with the async def
syntax cannot.
Here are two minimal, ostensibly equivalent coroutine functions defined with the two syntaxes:
import asyncio
@asyncio.coroutine
def decorated(x):
yield from x
async def native(x):
await x
Although the bytecode for these two functions is almost identical:
>>> 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
... the only difference being GET_YIELD_FROM_ITER
vs GET_AWAITABLE
, they behave completely differently when an attempt is made to iterate over the objects they return:
>>> 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
Obviously 'foo'
is not an awaitable, so the attempt to call native()
with it doesn't make much sense, but the point is hopefully clear that the coroutine
object it returns is not iterable, regardless of its argument.
A more detailed investigation of the async
/await
syntax by Brett Cannon: How the heck does async/await work in Python 3.5? covers this difference in more depth.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…