【问题标题】:Recursive generators and send()递归生成器和 send()
【发布时间】:2011-07-22 02:23:47
【问题描述】:

有谁知道 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)

【问题讨论】:

  • 还有其他人想起(cdr(car(cdr(cdr(cdr(car(car lst)))))))吗?

标签: python recursion generator


【解决方案1】:

要意识到的重要一点是 send() 也会消耗!
来自http://docs.python.org/reference/expressions.html#generator.send

恢复执行并将一个值“发送”到生成器函数中。 value 参数成为当前 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

【讨论】:

  • 啊,伙计,不知道我是怎么错过的。但这真的很不幸——不得不使用while True 而不是for...in 确实使得使用生成器send() 方法没有吸引力。
  • 对于你正在做的事情,是的。生成器有两个用途——迭代或协程的开发。由于您想坚持使用迭代器协议,并且您的代码示例不需要不断与协程通信,我相信您真的只需要指定是否在顶层递归遍历一次(而不是指定 after每次消费)。如果是这种情况,只需将其作为生成器的参数 - def walk(obj, recurse = True): ... - 在创建 walk 生成器时传递一次。
  • 好吧,我实际上确实需要控制每个级别的递归。真正的用例是遍历对象层次结构以确定是否需要将数据保存到数据库中(层次结构和标准都相当复杂)。传入谓词函数来控制递归可能更简洁。
  • 也许你可以让协程通过可变对象共享状态?它们在单个上下文中相互让步,因此更改状态是安全的。 shared_state = {"recurse" : True}; gen = walk(obj, shared_state); for i, x in enumerate(gen): ...; shared_state["recurse"] = False.
  • 在 Python 3 中将 x = walker.next() 替换为 x = next(walker)
猜你喜欢
  • 1970-01-01
  • 2019-08-25
  • 2021-05-22
  • 2020-08-04
  • 2016-05-03
  • 1970-01-01
  • 1970-01-01
  • 2013-11-18
相关资源
最近更新 更多