Python生成器中的奇怪错误
我有一个Knuth算法(“跳舞链接”)的实现,它的行为非常奇怪。我找到了一个解决办法,但这就像魔术一样。下面的脚本测试N queens问题的代码。错误出现在第一个函数中,Python生成器中的奇怪错误,python,python-3.x,Python,Python 3.x,我有一个Knuth算法(“跳舞链接”)的实现,它的行为非常奇怪。我找到了一个解决办法,但这就像魔术一样。下面的脚本测试N queens问题的代码。错误出现在第一个函数中,solve。参数limit应限制生成的解决方案数量,默认值0表示“生成所有解决方案” 该代码用于生成5皇后问题的前6个解决方案,并且运行良好。(见电话 solutions = [s for s in solve(primary, rows, secondary, 6)] 第80行)实际上有10个解决方案,如果我要10个解决方案
solve
。参数limit
应限制生成的解决方案数量,默认值0表示“生成所有解决方案”
该代码用于生成5皇后问题的前6个解决方案,并且运行良好。(见电话
solutions = [s for s in solve(primary, rows, secondary, 6)]
第80行)实际上有10个解决方案,如果我要10个解决方案,我都会得到。如果我离开了限制,那么电话就被取消了
solutions = [s for s in solve(primary, rows, secondary)]
主程序打印十个空列表作为解决方案,[]
中的代码打印实际解决方案。如果我超过15,同样的事情也会发生
当我将生成器转换为第80行中的列表时,问题似乎出现了。如果我把注释掉的行放回第78行和第79行,并注释掉第80行之后的所有内容,那么程序将按照我的预期工作。但我不明白这一点;我经常列出生成器以这种方式返回的对象
另一件更奇怪的事情是,如果我把第13行改为
yield list(solution)
然后,第80行的代码在所有情况下都可以正常工作。我不记得我最初编写代码时是如何偶然发现这个乱七八糟的东西的。我今天查看了它,并将产量列表(解决方案)
更改为产量解决方案
,这时缺陷变得明显。我不能理解这一点<代码>解决方案已经是一个列表。事实上,我已经试着添加这一行
assert solution == list(solution)
就在第13行之前,它从不引发AssertionError
我完全不知所措。我试图制作一个更小的脚本来重现这种行为,但我一直未能做到。你明白发生了什么事吗?你能给我解释一下吗 看到代码之前的预测:
产生列表(解决方案)
产生解决方案的浅拷贝<代码>生成解决方案生成解决方案列表本身,因此当您随后对该列表进行变异时,您会遇到麻烦
看起来我是对的。:-)较短版本:
def weird(solution):
for i in range(len(solution)):
yield solution
solution.pop()
其中:
In [8]: result = list(weird(['a','b','c']))
In [9]: result
Out[9]: [[], [], []]
因为
In [10]: [id(x) for x in result]
Out[10]: [140436644005128, 140436644005128, 140436644005128]
但是如果我们收益列表(解决方案)
相反,我们得到
In [15]: list(less_weird(['a','b','c']))
Out[15]: [['a', 'b', 'c'], ['a', 'b'], ['a']]
首先,我们看到一个可变的默认参数,这是一个坏主意,但实际上不是您看到的错误的原因:
def solver(Cols, Rows, SecondaryIDs, solution=[]):
live=[col for col in Cols if col not in SecondaryIDs]
if not live:
yield solution
在这里,您将得到解决方案
else:
col = min(live, key = lambda col: len(Cols[col]))
for row in list(Cols[col]):
solution.append(row)
columns = select(Cols, Rows, row)
for soln in solver(Cols, Rows, SecondaryIDs, solution):
yield soln
deselect(Cols, Rows, row, columns)
solution.pop()
在这里,您可以对之前生成的列表进行变异。在看到代码之前进行预测:
生成列表(解决方案)
生成解决方案的浅拷贝<代码>生成解决方案生成解决方案列表本身,因此当您随后对该列表进行变异时,您会遇到麻烦
看起来我是对的。:-)较短版本:
def weird(solution):
for i in range(len(solution)):
yield solution
solution.pop()
其中:
In [8]: result = list(weird(['a','b','c']))
In [9]: result
Out[9]: [[], [], []]
因为
In [10]: [id(x) for x in result]
Out[10]: [140436644005128, 140436644005128, 140436644005128]
但是如果我们收益列表(解决方案)
相反,我们得到
In [15]: list(less_weird(['a','b','c']))
Out[15]: [['a', 'b', 'c'], ['a', 'b'], ['a']]
首先,我们看到一个可变的默认参数,这是一个坏主意,但实际上不是您看到的错误的原因:
def solver(Cols, Rows, SecondaryIDs, solution=[]):
live=[col for col in Cols if col not in SecondaryIDs]
if not live:
yield solution
在这里,您将得到解决方案
else:
col = min(live, key = lambda col: len(Cols[col]))
for row in list(Cols[col]):
solution.append(row)
columns = select(Cols, Rows, row)
for soln in solver(Cols, Rows, SecondaryIDs, solution):
yield soln
deselect(Cols, Rows, row, columns)
solution.pop()
在这里,你改变了你之前得到的相同列表
问题是,您正在生成一个列表,然后在其中添加和删除项目。当调用方检查列表时,它已更改。您需要返回解决方案的冻结副本,以确保保留每个yield
语句的结果。其中任何一项都可以:
yield list(solution)
yield solution[:]
yield tuple(solution)
问题是,您正在生成一个列表,然后在其中添加和删除项目。当调用方检查列表时,它已更改。您需要返回解决方案的冻结副本,以确保保留每个yield
语句的结果。其中任何一项都可以:
yield list(solution)
yield solution[:]
yield tuple(solution)
可能是另一种情况见不到。默认参数似乎不是这种情况code@iBug是的,我不明白怎么会这样。我会改变它,只是为了确定,但我并不乐观。可能是另一个看不见的情况。默认参数似乎不是这种情况code@iBug是的,我不明白怎么会这样。我会改变它,只是为了确定,但我并不乐观。