具有递归性的奇怪行为 Python 生成器



我最近研究了一种算法,用于在Sagemath的二合字母上列出生成树。

这样做时,我和我的同事发现了一种我们并不完全理解的奇怪行为。

对于规范方面,Sagemath使用的是Python 2.7和import absolute import from __future__

该算法逐个列出生成树,使用 "yield" 关键字和对自身的递归调用。 有一次,我们在递归调用之前进行测试,以确保它不会触发错误。

但是,如果我们删除测试并让算法生成错误而不捕获它,生成器不会自行停止,而是传递给下一个递归调用。

如果我能给出一个图解的解释,假设每个递归调用中有 3 个步骤,我们得到了这样的内容:

  • 级别 0 - 步骤 1
  • 级别 0 - 步骤 2
    • 级别 1 - 步骤 1
    • 级别 1 - 步骤 2
      • 级别 2 - 步骤 1 <- 错误发生在此处,但不停止生成
    • 级别 1 - 步骤 3
      • 级别 2 - 步骤 1
      • 级别 2 - 产量 -级别 0 - 步骤 3

如果我们通过"try catch"捕获错误,那么算法将自行停止。

我让代码在这里;如果你不了解Sagemath,我不确定你能抓住所有东西,但其中大部分是可以理解的。我将发表具体评论以突出显示有趣的部分并删除代码的某些部分。

我希望有人能解释我的这种行为;就像生成器对象可以在递归调用期间处理异常并在生成时忽略它们。

def _rec_spanning_trees():
if len(list_merged_edges) == self.order()-1: # CONDITION TO YIELD
for indexes in product(*list_merged_edges):
yield DiGraph([list_edges[index] for index in indexes], format='list_of_edges', pos=self.get_pos())
# part removed
# THIS HERE !
# "outgoing_edge_iterator" is raising an error if D does not have any edges
s, x, l = D.outgoing_edge_iterator(source).next()
# HERE ! 
# If I remove the "if(len(...." then the line above will raise an error sometime, but do not if I let it
D.delete_edge(s, x, l)
if len(list(D.depth_first_search(source))) == D.order():
for tree in _rec_spanning_trees():
yield tree
D.add_edge(s, x, l)
# part removed
for tree in _rec_spanning_trees():
yield tree
# part removed

D.outgoing_edge_iterator(source).next()会产生异常,当然,但该异常StopIteration。这是用于发出迭代器结束信号的异常,当它从生成器传播到上面的for循环时,for循环将其视为循环已完成。

此行为在最近的 Python 版本中已更改;生成器内部引发的StopIteration现在将转换为RuntimeError

最新更新