生成器推导式与列表推导式的输出有何不同?

26

当使用列表生成式和生成器生成式时我得到了不同的输出结果,这是预期行为还是错误?

考虑以下设置:

all_configs = [
    {'a': 1, 'b':3},
    {'a': 2, 'b':2}
]
unique_keys = ['a','b']

如果我运行以下代码,则会得到:

print(list(zip(*( [c[k] for k in unique_keys] for c in all_configs))))
>>> [(1, 2), (3, 2)]
# note the ( vs [
print(list(zip(*( (c[k] for k in unique_keys) for c in all_configs))))
>>> [(2, 2), (2, 2)]

这是在Python 3.6.0上:

Python 3.6.0 (default, Dec 24 2016, 08:01:42)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
4个回答

37
在列表推导式中,表达式会被立即计算。在生成器表达式中,表达式只有在需要时才会被查找。 因此,当生成器表达式在for c in all_configs上进行迭代时,它引用了,但只有在循环结束后才查找,因此它仅使用两个元组的最新值。相比之下,列表推导式会立即计算,因此它会创建一个包含第一个值的元组和另一个包含第二个值的元组。 考虑这个简单的例子:
>>> r = range(3)
>>> i = 0
>>> a = [i for _ in r]
>>> b = (i for _ in r)
>>> i = 3
>>> print(*a)
0 0 0
>>> print(*b)
3 3 3

创建a时,解释器立即创建了那个列表,并在评估完i的值后立即查找。创建b时,解释器只是设置了那个生成器,并没有实际迭代它并查找i的值。 print调用告诉解释器评估这些对象。a已经以旧的i值作为完整列表存在于内存中,但此时b被评估,在查找i的值时,它发现了新的值。


12
为了看到发生了什么,请用具有副作用的函数替换 c[k]
def f(c,k):
    print(c,k)
    return c[k]
print("listcomp")
print(list(zip(*( [f(c,k) for k in unique_keys] for c in all_configs))))
print("gencomp")
print(list(zip(*( (f(c,k) for k in unique_keys) for c in all_configs))))

输出:

listcomp
{'a': 1, 'b': 3} a
{'a': 1, 'b': 3} b
{'a': 2, 'b': 2} a
{'a': 2, 'b': 2} b
[(1, 2), (3, 2)]
gencomp
{'a': 2, 'b': 2} a
{'a': 2, 'b': 2} a
{'a': 2, 'b': 2} b
{'a': 2, 'b': 2} b
[(2, 2), (2, 2)]

c在生成器表达式中是在外部循环完成后进行评估的:

c承载了外部循环中最后一个值。

在列表推导的情况下,c会一次性地进行评估。

(注意由于zip时的执行和一次性执行,aabbabab也有所不同)

请注意,您可以通过将c传递给map来保持“生成器”方式(不创建临时列表),从而存储当前值:

print(list(zip(*( map(c.get,unique_keys) for c in all_configs))))

在Python 3中, map 不会创建一个 list ,但结果仍然是正确的: [(1, 2),(3, 2)]

6
这是因为zip(*)调用导致外部生成器的评估,并且此外部返回了另外两个生成器。
(c[k], print(c)) for k in unique_keys)

把外部生成器中的c移动到第二个字典进行评估:{'a': 2, 'b':2}

现在,当我们单独评估这些生成器时,它们会在某个地方寻找c,因为它的值现在是{'a': 2, 'b':2},所以你会得到输出结果为[(2, 2), (2, 2)]

演示:

>>> def my_zip(*args):
...     print(args)
...     for arg in args:
...         print (list(arg))
...
... my_zip(*((c[k] for k in unique_keys) for c in all_configs))
...

输出:

# We have two generators now, means it has looped through `all_configs`.
(<generator object <genexpr>.<genexpr> at 0x104415c50>, <generator object <genexpr>.<genexpr> at 0x10416b1a8>)
[2, 2]
[2, 2]

另一方面,列表推导式会立即进行评估,并且可以获取当前值c的值,而不是其最后一个值。


如何强制它使用正确的c值?

使用内部函数和生成器函数。内部函数可以使用默认参数帮助我们记住c的值。

>>> def solve():
...     for c in all_configs:
...         def func(c=c):
...             return (c[k] for k in unique_keys)
...         yield func()
...

>>>

>>> list(zip(*solve()))
[(1, 2), (3, 2)]

-1

两者都是生成器对象。第一个只是一个生成器,而第二个是一个嵌套的生成器。

print list( [c[k] for k in unique_keys] for c in all_configs)
[[1, 3], [2, 2]]
print list( (c[k] for k in unique_keys) for c in all_configs)
[<generator object <genexpr> at 0x000000000364A750>, <generator object <genexpr> at 0x000000000364A798>]

当您在第一个表达式中使用zip(*)时,因为它是一个生成器,将返回与list()相同的列表,所以不会发生任何事情。因此,它返回您预期的输出。第二次它压缩生成器,创建一个包含第一个生成器的列表和一个包含第二个生成器的列表。这些生成器本身的结果与第一个表达式的生成器不同。

这将是列表压缩:

   print [c[k] for k in unique_keys for c in all_configs]
   [1, 2, 3, 2]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接