我最近一直在使用asyncio,虽然我开始对它的工作原理有所了解,但我还是无法做到这一点。我不确定是不是因为我的构造错误了,或者有什么理由说我为什么要做的事情没有意义。
简而言之,我希望能够迭代一个让步的asyncio.coroutine。例如,我希望能够做到这样的事情:
@asyncio.coroutine
def countdown(n):
while n > 0:
yield from asyncio.sleep(1)
n = n - 1
yield n
@asyncio.coroutine
def do_work():
for n in countdown(5):
print(n)
loop.run_until_complete(do_work())
然而,这引起了asyncio的一个例外。我尝试过其他的东西,比如for n in (yield from countdown(5)): ...
但是它也提供了类似的不透明的运行时异常。
我无法立即明白为什么你不应该做这样的事情,但是我已经达到理解正在发生的事情的极限。
所以:
如果这个问题不清楚,请告诉我!
答案 0 :(得分:5)
在asyncio协同程序中,您应该使用yield from
而不是yield
。
这是设计的。 yield from
的参数应该只是另一个协程或asyncio.Future
实例。
协程本身的调用应该与yield from
再次一起使用yield from countdown(5)
。
对于您的情况,我建议使用队列:
import asyncio
@asyncio.coroutine
def countdown(n, queue):
while n > 0:
yield from asyncio.sleep(1)
n = n - 1
yield from queue.put(n)
yield from queue.put(None)
@asyncio.coroutine
def do_work():
queue = asyncio.Queue()
asyncio.async(countdown(5, queue))
while True:
v = yield from queue.get()
if v:
print(v)
else:
break
asyncio.get_event_loop().run_until_complete(do_work())
好吧,您可以使用检查countdown
产生的值,以下示例有效。但我认为这是反模式:
太容易弄得一团糟
无论如何,您无法使用countdown
函数撰写itertools
个电话。我的意思是sum(countdown(5))
或itertools.accumulate(countdown(5))
。
无论如何,在协程中混合yield
和yield from
的示例:
import asyncio
@asyncio.coroutine
def countdown(n):
while n > 0:
yield from asyncio.sleep(1)
n = n - 1
yield n
@asyncio.coroutine
def do_work():
for n in countdown(5):
if isinstance(n, asyncio.Future):
yield from n
else:
print(n)
asyncio.get_event_loop().run_until_complete(do_work())
答案 1 :(得分:4)
在Python 3.5中,引入了async for
语法。但是,仍然缺少异步迭代器函数语法(即yield
函数中禁止async
。这是一个解决方法:
import asyncio
import inspect
class escape(object):
def __init__(self, value):
self.value = value
class _asynciter(object):
def __init__(self, iterator):
self.itr = iterator
async def __aiter__(self):
return self
async def __anext__(self):
try:
yielded = next(self.itr)
while inspect.isawaitable(yielded):
try:
result = await yielded
except Exception as e:
yielded = self.itr.throw(e)
else:
yielded = self.itr.send(result)
else:
if isinstance(yielded, escape):
return yielded.value
else:
return yielded
except StopIteration:
raise StopAsyncIteration
def asynciter(f):
return lambda *arg, **kwarg: _asynciter(f(*arg, **kwarg))
然后您的代码可以写成:
@asynciter
def countdown(n):
while n > 0:
yield from asyncio.sleep(1)
#or:
#yield asyncio.sleep(1)
n = n - 1
yield n
async def do_work():
async for n in countdown(5):
print(n)
asyncio.get_event_loop().run_until_complete(do_work())
要了解新语法以及此代码的工作原理,请参阅PEP 492
答案 2 :(得分:1)
更新:似乎是python 3.5 supports this even better natively:
遇到同样的问题(受到aio-s3中的代码的启发),我觉得应该有更优雅的解决方案。
import asyncio
def countdown(number):
@asyncio.coroutine
def sleep(returnvalue):
yield from asyncio.sleep(1)
return returnvalue
for n in range(number, 0, -1):
yield sleep(n)
@asyncio.coroutine
def print_countdown():
for future in countdown(5):
n = yield from future
print ("Counting down: %d" % n)
asyncio.get_event_loop().run_until_complete(print_countdown())
理由:countdown
方法产生期货,每个人将在1秒钟睡眠后解决所提供的数字。
print_countdown
函数占用第一个未来,yield from
- 它(它将暂停直到它被解析)并获得预期结果:n
。