Python生成器中的奇怪错误

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个解决方案

我有一个Knuth算法(“跳舞链接”)的实现,它的行为非常奇怪。我找到了一个解决办法,但这就像魔术一样。下面的脚本测试N queens问题的代码。错误出现在第一个函数中,
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是的,我不明白怎么会这样。我会改变它,只是为了确定,但我并不乐观。