【问题标题】:Different result unrolling loop - Python generators [duplicate]不同的结果展开循环 - Python生成器[重复]
【发布时间】:2017-10-17 15:27:11
【问题描述】:

当我使用循环将两个生成器附加到列表时,第一个生成器复制第二个生成器的输出。当我展开循环时,我得到了与预期不同的输出。

以下代码演示了该问题。

import itertools

iterators = itertools.tee(itertools.repeat(('a', 0), 5), 2)
result = []
result.append(r[0] for r in iterators[0])
result.append(r[1] for r in iterators[1])

# As expected
print('Written out...')
print(list(result[0])) # ['a', 'a', 'a', 'a', 'a']
print(list(result[1])) # [0, 0, 0, 0, 0]


# Now do it again but use a loop
iterators = itertools.tee(itertools.repeat(('a', 0), 5), 2)
result = []
for index in [0, 1]:
    result.append(r[index] for r in iterators[index])

# This time both lists are of the second item.
print('With a loop...')
print(list(result[0])) # [0, 0, 0, 0, 0] <--- Huh?!
print(list(result[1])) # [0, 0, 0, 0, 0]

为什么循环版本不能按我预期的那样工作?我该怎么办?


解决方案

现在这已作为重复关闭我无法发布另一个答案,但为了记录,这是我最终使用的解决方案。

@MikeMüller 指出的问题是索引rindex 实例是后期绑定的。以下通过为循环中 index 的每个值创建一个新的局部变量实例 i 来强制进行早期绑定:

for index, it in enumerate(iterators):
    g = lambda i: (r[i] for r in it) # force early binding on index
    result.append(g(index))

(我也很喜欢 Mike 的建议,即一直使用生成器,但不幸的是,我需要实现外部生成器(result),以便我可以重复引用 result 中的各个元素。但 @987654330 @ 与我的原始循环代码具有相同的行为。)

【问题讨论】:

  • 你想得到什么输出?
  • 您只有一个index 变量。两个生成器都使用它。和 JavaScript 一样,我会尝试找到一个 Python 副本:stackoverflow.com/questions/750486/…
  • 请记住,在您调用 list 并且您正在捕获 index 之前,生成器不会运行。将打印内容粘贴到循环中,看看会发生什么。

标签: python python-3.x generator


【解决方案1】:

修复

您需要提前使用您的迭代器:

for index in [0, 1]:
    result.append(list(r[index] for r in iterators[index]))

获得同等效果。

现在:

print('With a loop...')
print(result[0])
print(result[1])

输出:

With a loop...
['a', 'a', 'a', 'a', 'a']
[0, 0, 0, 0, 0]

在循环之后说明这个集合index = 0

iterators = itertools.tee(itertools.repeat(('a', 0), 5), 2)
result = []
for index in [0, 1]:
    result.append(r[index] for r in iterators[index])
index = 0
print('With a loop...')
print(list(result[0]))
print(list(result[1]))

现在tee 的第一部分被使用了两次,因为r[index] 总是意味着r[0]

With a loop...
['a', 'a', 'a', 'a', 'a']
['a', 'a', 'a', 'a', 'a']

原因

index 被延迟应用,即当您实际转换为列表时。 由于循环后索引为1,因此它在r[index] 中使用了此1 两次,并且您在迭代器中获得了第二项两次。

另类

一直使用迭代器直到消费:

iterators = itertools.tee(itertools.repeat(('a', 0), 5), 2)
result = ((r[index] for r in iterators[index]) for index in [0, 1])
for res in result:
    print(list(res))

输出:

['a', 'a', 'a', 'a', 'a']
[0, 0, 0, 0, 0]

【讨论】:

  • 这也是为什么如果将append 替换为extend 效果很好:它会按时消耗迭代器。
  • 我的第一个猜测是 index 被关闭了,所以它对两个生成器都取其最后一个值 (1)。所以我尝试在循环后设置 index = -1 期望索引超出范围异常。但我没有,也没有任何改变。 (我现在意识到这是因为 -1 表示最后一个元素,在上下文中与 1 相同。)
  • 谢谢。就我而言,您的“替代方案”是正确的答案。如果更早使用迭代器是一种选择,那么显而易见的解决方案是将元组的初始迭代器转换为列表,并使用几个简单的列表推导提取单个元素。简单得多,但如果整个序列不适合内存,那就不好了。
  • 另一个有趣的事情是,虽然生成器表达式中的第一个 index 被延迟评估,但第二个是 evaluated immediately (从第一个副本链接),这解释了为什么我没有看到生成器第一个结果后筋疲力尽..你真的必须知道细节才能安全地使用这个东西! :-)
  • 是的,iterators[index] 被立即评估,但 r[index] 被延迟评估。这是因为r[index] 是“循环变量”,可能会被多次评估,而iterators[index] 只会被评估一次。
猜你喜欢
  • 2019-02-18
  • 1970-01-01
  • 2019-01-07
  • 2021-02-25
  • 1970-01-01
  • 1970-01-01
  • 2015-05-12
  • 2013-10-19
  • 1970-01-01
相关资源
最近更新 更多