为什么在使用带有 asyncio 的协程的列表推导式时会得到不同的结果?
Why am I getting different results when using a list comprehension with coroutines with 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()
函数会得到不同的结果?
解决您的问题的方法是在第三个函数的 return 中放置 next(...)
而不是 ...
,或者更好地编写 return list((yield from coro()) for i in range(3))
(感谢@ zch 对于这个想法),或者更好地保留第一个函数。
关键是第二个函数不是生成器。 return 是一个理解生成器,只是一个普通的函数。
例如这段代码在外部生成器中有效:
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
然后通过迭代结果使第二个函数成为生成器,参见 here,第 143 行。
相比之下,第一个和第三个函数实际上是生成器,而 @coroutine
装饰器只是 return 本身,参见 here,第 136-137 行。在第一种情况下,生成器 returns 列表(实际上引发 StopIteration(['foo', 'foo', 'foo'])
)。在第三种情况下,它 return 是理解生成器。
我最初有一些代码将结果聚合到一个列表中。当我重构此代码以使用列表综合时,我得到了意想不到的结果:
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()
函数会得到不同的结果?
解决您的问题的方法是在第三个函数的 return 中放置 next(...)
而不是 ...
,或者更好地编写 return list((yield from coro()) for i in range(3))
(感谢@ zch 对于这个想法),或者更好地保留第一个函数。
关键是第二个函数不是生成器。 return 是一个理解生成器,只是一个普通的函数。
例如这段代码在外部生成器中有效:
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
然后通过迭代结果使第二个函数成为生成器,参见 here,第 143 行。
相比之下,第一个和第三个函数实际上是生成器,而 @coroutine
装饰器只是 return 本身,参见 here,第 136-137 行。在第一种情况下,生成器 returns 列表(实际上引发 StopIteration(['foo', 'foo', 'foo'])
)。在第三种情况下,它 return 是理解生成器。