迭代asyncio.coroutine

时间:2014-05-17 09:54:34

标签: python python-3.4 python-asyncio

我最近一直在使用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)): ...但是它也提供了类似的不透明的运行时异常。

我无法立即明白为什么你不应该做这样的事情,但是我已经达到理解正在发生的事情的极限。

所以:

  • 如果可以这样做,我该怎么办?
  • 如果不可能,为什么不呢?

如果这个问题不清楚,请告诉我!

3 个答案:

答案 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产生的值,以下示例有效。但我认为这是反模式:

  1. 太容易弄得一团糟

  2. 无论如何,您无法使用countdown函数撰写itertools个电话。我的意思是sum(countdown(5))itertools.accumulate(countdown(5))

  3. 无论如何,在协程中混合yieldyield 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