递归生成器与send()

3 投票
1 回答
545 浏览
提问于 2025-04-16 22:02

有没有人知道在递归使用生成器时,send()是怎么工作的?我本以为值会传递给当前的生成器,然后再传递给递归的生成器……但似乎并不是这样?这里有一些示例代码:

def Walk(obj):
  recurse = (yield obj)
  if not recurse:
    print 'stop recurse:', recurse
    return

  if isinstance(obj, list):
    print 'is list:', obj
    for item in obj:
      print 'item loop:', item
      walker = Walk(item)
      for x in walker:
        print 'item walk:', x
        recurse = (yield x)
        print 'item walk recurse:', recurse
        walker.send(recurse)

root = ['a', ['b.0', ['b.0.0']]]

walker = Walk(root)
for i, x in enumerate(walker):
  print i, x
  print 'send true'
  walker.send(True)

我希望的输出是每一层递归的每个值:

0 ['a', ['b.0', ['b.0.0']]]
1 'a'
2 ['b.0', ['b.0.0']]
3 'b.0'
4 ['b.0.0']
5 'b.0.0'

但实际上发生的是:

0 ['a', ['b.0', ['b.0.0']]]
send true
is list: ['a', ['b.0', ['b.0.0']]]
item loop: a
item walk: a
item walk recurse: None
stop recurse: None

看起来内部循环中的 recurse = (yield) 并没有等到一个值被发送过来。或者说是其他什么原因。其实不太清楚内部循环的 recurse 值是怎么变成 None 的;它的调用者确实调用了 send()

最终的目标基本上是递归地遍历一个树形结构,但希望最上层的调用者能够指定什么时候 进入一个子结构。例如:

walker = Walk(root)
for node in walker:
  if CriteriaMet(node):
    walker.send(True)
  else:
    walker.send(False)

1 个回答

6

重要的是要明白,send() 这个方法也是会消耗值的!
根据 这个链接 的解释:

send() 方法会恢复执行并“发送”一个值到生成器函数里。
这个值会成为当前 yield 表达式的结果。
send() 方法会返回生成器下一个产生的值,或者如果生成器结束时没有再产生值,就会抛出 StopIteration 异常。当第一次调用 send() 启动生成器时,必须传入 None 作为参数,因为那时没有 yield 表达式可以接收值。

下面是你代码的一个快速修改版本,让它输出符合预期:

def Walk(obj):
  recurse = (yield obj)
  if not recurse:
    #print 'stop recurse:', recurse
    return

  if isinstance(obj, list):
    #print 'is list:', obj
    for item in obj:
      #print 'item loop:', item
      walker = Walk(item)

      recurse = None #first send must be None
      while True:
        try:
          x = walker.send(recurse)
        except StopIteration:
          break
        #print 'item walk:', x
        recurse = (yield x)
        #print 'item walk recurse:', recurse

root = ['a', ['b.0', ['b.0.0']]]

walker = Walk(root)
i = 0
x = walker.next()
while True:
  print i, x
  try:
    x = walker.send(True)
  except StopIteration:
    break
  i += 1

输出结果:

0 ['a', ['b.0', ['b.0.0']]]
1 a
2 ['b.0', ['b.0.0']]
3 b.0
4 ['b.0.0']
5 b.0.0

撰写回答