在 Tornado 协程中使用常规 Python 生成器

Using regular Python generator in Tornado coroutine

Python 生成器是一个很棒的功能。它允许我对复杂的、可能是递归的遍历逻辑进行编码,并将其与其用户分离。通常我像下面的代码一样使用它

TREE = {
  1: [2,3],
  2: [],
  3: [4,5],
  4: [6],
  5: [],
  6: []
  }   

def query_children(node):
    return TREE[node]

def walk_tree(root):
    # recursive tree traversal logic
    yield root
    children = query_children(root)
    for child in children:
        for node in walk_tree(child):
            yield node

def do_something():
   # nice linear iterator
   for node in walk_tree(root):
       print(node)

Tornado 使用生成器实现协程,这也是构建无回调异步函数的好方法。

但是,当我尝试同时使用两者时,我会感到困惑。

@gen.coroutine
def query_children(node):
    ...
    raise gen.Return(children)


def walk_tree(root):
    # recursive tree traversal logic
    yield root
    children = yield query_children(root)
    for child in children:
        for node in walk_tree(child):
            yield node


def do_something():
   # nice linear iterator
   for node in walk_tree(root):
       print(node)

在新的 walk_tree 中,第一个产量是常规 Python 产量。第二个产量是龙卷风的。他们可以一起工作吗?

我得到这个工作。我没有在非协程 walk_tree() 中使用 yield,而是 运行 它通过调用 IOLoop.run_sync 同步使用获取子程序。我是龙卷风新手。所以如果这是一个合法的解决方案或者是否有任何其他更好的方法,请评论。

TREE = {
  1: [2,3],
  2: [],
  3: [4,5],
  4: [6],
  5: [],
  6: []
  }   

@gen.coroutine
def query_children_async(node):
    raise gen.Return(TREE[node])

# this is a regular Python generator
def walk_tree(root):
    # recursive tree traversal logic
    yield root
    # call .result() of the Future
    children = IOLoop.instance().run_sync(lambda: query_children_async(root))
    for child in children:
        for node in walk_tree(child):
            yield node

@gen.coroutine
def do_something(root):
    # just collect the result
    result = [node for node in walk_tree(root)]
    raise gen.Return(result)

Edit 1. The original proposal using .result() does not work. I got "DummyFuture does not support blocking for results" when I have a non-trivial query_children_async().

Python生成器协议基于同步接口; asynchronous 代码不可能像协程一样作为生成器的一部分与 for 一起使用(协程最重要的规则:任何调用协程的东西都必须也是一个协程,或者至少知道协程。for 语句对它们一无所知,它就是调用你的生成器的东西)。相反,我建议使用 tornado.queues.Queue:

@gen.coroutine
def query_children(node):
    ...
    raise gen.Return(children)


def walk_tree(queue, root):
    # recursive tree traversal logic
    yield root
    children = yield query_children(root)
    for child in children:
        for node in walk_tree(child):
            yield queue.put(node)
     yield queue.put(None)


def do_something():
    queue = tornado.queues.Queue()
    IOLoop.current().spawn_callback(walk_tree, queue, root)
    while True:
        node = yield queue.get()
        if node is None:
            break
        print(node)