我有一个生成器,它从有向无环图(DAG)中产生节点,深度优先:
def depth_first_search(self):
yield self, 0 # root
for child in self.get_child_nodes():
for node, depth in child.depth_first_search():
yield node, depth+1
我可以迭代这样的节点
for node, depth in graph.depth_first_search():
# do something
如果满足某些条件,我希望能够从for循环告诉生成器停止在图中更深入。
我想出了以下使用外部函数的解决方案。
def depth_first_search(self, stop_crit=lambda n,d: False):
yield self, 0 # root
for child in self.get_child_nodes():
for node, depth in child.depth_first_search():
yield node, depth+1
if stop_crit(node, depth): break
这个解决方案迫使我在定义stop_crit之前声明我需要的变量,以便可以从中访问它们。
在Ruby中,yield返回块中的最后一个表达式,因此可以方便地用它来告诉生成器继续或停止。
在Python中实现此功能的最佳方法是什么?
答案 0 :(得分:7)
通常在Python中你只会停止使用生成器而忘记它。点。 (因此以通常的方式将东西留给垃圾收集器)
然而,通过使用generator.close()
,您可以立即强制立即启动发生器清理所有终结。
示例:
>>> def gen():
... try:
... for i in range(10):
... yield i
... finally:
... print "gen cleanup"
...
>>> g = gen()
>>> next(g)
0
>>> for x in g:
... print x
... if x > 3:
... g.close()
... break
...
1
2
3
4
gen cleanup
>>> g = gen()
>>> h = g
>>> next(g)
0
>>> del g
>>> del h # last reference to generator code frame gets lost
gen cleanup
答案 1 :(得分:2)
Coroutines(bassfriend提到他们)对于没有经验的人来说很棘手,所以这里有一个。我添加了一些测试代码,以便您可以看到它是如何工作的。
class Node(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
# the producing coroutine, it sends data to the consumer
def depth_first_search(self, consumer, depth=0):
""" `consumer` is a started coroutine that yields True to continue a branch """
if consumer.send((self, depth)): # continue this branch?
for child in self.get_child_nodes():
child.depth_first_search(consumer, depth+1)
def get_child_nodes(self):
for node in (self.left, self.right):
if node is not None:
yield node
def __repr__(self):
return "Node(val=%d)" % self.val
def coroutine(func):
""" decorator that declares `func` as a coroutine and starts it """
def starter(*args, **kwargs):
co = func(*args, **kwargs)
next(co) # corotines need to be started/advanced to the first yield
return co
return starter
# the consumer takes data and yields if it wants to continue
@coroutine
def consumer( continue_branch=lambda n,d:True ):
node, depth = (yield True) # first node
while True:
print node, depth # do stuff
node, depth = (yield continue_branch(node, depth))
# testing
tree = Node(5, Node(2, Node(3), Node(4)), Node(6, Node(7), Node(8))) #
cons = consumer()
tree.depth_first_search(cons)# yields all
print
stopper = consumer(lambda n,d: n.val > 2) # skips the children of Node 2
tree.depth_first_search(stopper)
诀窍在于,如果保留函数的角色,depth_first_search
会产生节点,最终会出现可怕的混乱...而是生成节点并发送对消费者而言。
Python对协同程序的支持有点尴尬(@coroutine
救援)。有一个非常nice tutorial for Python和大量资源用于依赖协程的语言,例如Lua。无论如何,这是一个值得探索的非常酷的概念: - )
答案 2 :(得分:2)
通常你不会告诉 iterable 检查条件,你在循环体中做到了这一点:
for node, depth in graph.depth_first_search():
if node meets condition:
# do something with node
break
# do something with node, its still referencing what you breaked on
此代码的优点是不会让任何人感到惊讶或混淆。
答案 3 :(得分:0)
天真的解决方案:
def depth_first_search(self):
yield self, 0 # root
for child in self.get_child_nodes():
for node, depth in child.depth_first_search():
if(yield node, depth+1):
yield None # for .send
return
你可以正常调用它,但你必须将iterable保存为abort:
it = graph.depth_first_search()
for node, depth in it: #this is why there should be pronouns for loop iterables
stuff(node,depth)
if quit: it.send(1)
# it.next() should raise StopIteration on the next for iteration
我认为现在可行。