Python:在生成器对象上调用 list() 会产生不正确的结果
Posted
技术标签:
【中文标题】Python:在生成器对象上调用 list() 会产生不正确的结果【英文标题】:Python: calling list() on generator object produces incorrect result 【发布时间】:2018-07-27 00:20:24 【问题描述】:我正在查看this question 的公认解决方案,它提供了一种算法的 Python 实现,用于按字典顺序生成唯一排列。我有一个稍微缩短的实现:
def permutations(seq):
seq = sorted(seq)
while True:
yield seq
k = l = None
for k in range(len(seq) - 1):
if seq[k] < seq[k + 1]:
l = k + 1
break
else:
return
(seq[k], seq[l]) = (seq[l], seq[k])
seq[k + 1:] = seq[-1:k:-1]
对我来说真正奇怪的是,如果我在这个函数的输出上调用list
,我会得到错误的结果。但是,如果我一次迭代这个函数的结果,我会得到预期的结果。
>>> list(permutations((1,2,1)))
[[2, 1, 1], [2, 1, 1], [2, 1, 1]]
>>> for p in permutations((1,2,1)):
... print(p)
...
[1, 1, 2]
[1, 2, 1]
[2, 1, 1]
^^^什么?!另一个例子:
>>> list(permutations((1,2,3)))
[[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1]]
>>> for p in permutations((1,2,3)):
... print(p)
...
[1, 2, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
列表推导式也会产生不正确的值:
>>> [p for p in permutations((1,2,3))]
[[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1]]
我不知道这里发生了什么!我以前没见过这个。我可以编写使用生成器的其他函数,但我不会遇到这种情况:
>>> def seq(n):
... for i in range(n):
... yield i
...
>>> list(seq(5))
[0, 1, 2, 3, 4]
在我上面的示例中发生了什么导致了这种情况?
【问题讨论】:
@StephenRauch 好吧,我当然没有期望不同的行为取决于我是迭代结果还是调用list
。
旁白:你为什么有k = None
和l = None
?如果您删除它们,您应该会得到相同的行为。
如果 k
和 l
没有在 for 循环之外声明,那么当我尝试改变下面的 seq
时,它们就不会出现。
我认为这不是真的。 for
循环中的索引变量在 Python2 和 Python3 的封闭范围内都是可见的。例如,请参阅 eli.thegreenplace.net/2015/… 或 docs.python.org/dev/reference/… 或删除该行并查看它仍然有效。
你知道什么!谢谢@Robᵩ
【参考方案1】:
你在生成器中修改seq
,在你生成它之后。您不断产生相同的对象并对其进行修改。
(seq[k], seq[l]) = (seq[l], seq[k]) # this mutates seq
seq[k + 1:] = seq[-1:k:-1] # this mutates seq
注意,您的list
包含同一个对象多次:
In [2]: ps = list(permutations((1,2,1)))
In [3]: ps
Out[3]: [[2, 1, 1], [2, 1, 1], [2, 1, 1]]
In [4]: [hex(id(p)) for p in ps]
Out[4]: ['0x105cb3b48', '0x105cb3b48', '0x105cb3b48']
所以,试试yield
复制一份:
def permutations(seq):
seq = sorted(seq)
while True:
yield seq.copy()
k = None
l = None
for k in range(len(seq) - 1):
if seq[k] < seq[k + 1]:
l = k + 1
break
else:
return
(seq[k], seq[l]) = (seq[l], seq[k])
seq[k + 1:] = seq[-1:k:-1]
然后,瞧:
In [5]: def permutations(seq):
...: seq = sorted(seq)
...: while True:
...: yield seq.copy()
...: k = None
...: l = None
...: for k in range(len(seq) - 1):
...: if seq[k] < seq[k + 1]:
...: l = k + 1
...: break
...: else:
...: return
...:
...: (seq[k], seq[l]) = (seq[l], seq[k])
...: seq[k + 1:] = seq[-1:k:-1]
...:
In [6]: ps = list(permutations((1,2,1)))
In [7]: ps
Out[7]: [[1, 1, 2], [1, 2, 1], [2, 1, 1]]
至于为什么 for 循环中的 print
ing 没有显示这种行为,这是因为在迭代的那一刻 seq
具有“正确”值,因此请考虑:
In [10]: result = []
...: for i, x in enumerate(permutations((1,2,1))):
...: print("iteration ", i)
...: print(x)
...: result.append(x)
...: print(result)
...:
iteration 0
[1, 1, 2]
[[1, 1, 2]]
iteration 1
[1, 2, 1]
[[1, 2, 1], [1, 2, 1]]
iteration 2
[2, 1, 1]
[[2, 1, 1], [2, 1, 1], [2, 1, 1]]
【讨论】:
啊哈,是的,好吧,我相信我理解为什么我一次又一次地拿回相同的物品的解释。谢谢!对我来说仍然有点不清楚的是,为什么当我使用for p in permuatations(seq): print(p)
成语时会得到不同的结果。
@JawguyChooser 因为那是当时打印序列。在生成器上调用next
(隐式在for循环中)之前,您不要修改它。`如果您执行了result = []
之类的操作,那么for x in permutations(...): print(x); result.append(x); print(result)
应该会清楚发生了什么。
啊哈,我想我跟着。这是一个有趣的微妙之处感谢您的及时和明确的回答!
@JawguyChooser 添加了示例。顺便说一句,您应该阅读以下内容:nedbatchelder.com/text/names.html 关于 python 变量的工作原理。一旦你了解它,发生的事情应该是完全显而易见的。
是的,谢谢,我过去也遇到过这个问题,不知何故,这里的生成器上下文让我失望了。你的例子中的结果数组似乎随着循环的每次迭代而改变,真是令人惊叹!以上是关于Python:在生成器对象上调用 list() 会产生不正确的结果的主要内容,如果未能解决你的问题,请参考以下文章