我最近研究了一种算法,用于在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
。