生成器理解列表理解的不同输出?

时间:2017-03-15 09:33:09

标签: python

使用列表推导与生成器理解时,我得到不同的输出。这是预期的行为还是一个错误?

考虑以下设置:

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 个答案:

答案 0 :(得分:37)

在列表理解中,表达式被急切地评估。在生成器表达式中,只会根据需要查找它们。

因此,当生成器表达式迭代for c in all_configs时,它引用c[k]但仅在循环完成后查找c,因此它仅使用两个元组的最新值。相比之下,列表理解会立即进行评估,因此它会创建一个元组,其第一个值为c,另一个元组的第二个值为c

考虑这个小例子:

>>> 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的值时,它找到了新的价值。

答案 1 :(得分: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会立即进行评估。

(请注意,aabb vs abab也是因为执行时同时执行拉伸与执行

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

print(list(zip(*( map(c.get,unique_keys) for c in all_configs))))
在Python 3中,map没有创建list,但结果仍然正常:[(1, 2), (3, 2)]

答案 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)]

答案 3 :(得分:-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]