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

Bas*_*Bas 26 python

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

请考虑以下设置:

all_configs = [
    {'a': 1, 'b':3},
    {'a': 2, 'b':2}
]
unique_keys = ['a','b']
Run Code Online (Sandbox Code Playgroud)

如果我然后运行以下代码,我得到:

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)]
Run Code Online (Sandbox Code Playgroud)

这是在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
Run Code Online (Sandbox Code Playgroud)

Tig*_*kT3 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
Run Code Online (Sandbox Code Playgroud)

创建时a,解释器立即创建该列表,i在评估后立即查找该值.在创建时b,解释器只是设置了该生成器,并且实际上没有迭代它并查找其值i.该print电话告诉解释,以评估这些对象.a已经作为内存中的完整列表存在,具有旧值i,但b在该点进行了评估,当它查找值时i,它找到了新值.


Jea*_*bre 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))))
Run Code Online (Sandbox Code Playgroud)

输出:

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)]
Run Code Online (Sandbox Code Playgroud)

c 在外部循环完成后评估生成器表达式:

c 承载它在外循环中的最后一个值.

在列表理解案例中,立即c进行评估.

(请注意aabbvs abab也因为执行时拉链与执行一次执行)

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

print(list(zip(*( map(c.get,unique_keys) for c in all_configs))))
Run Code Online (Sandbox Code Playgroud)

在Python 3中,map没有创建list,但结果仍然可以:[(1, 2), (3, 2)]


Ash*_*ary 6

这种情况正在发生,因为zip(*)调用导致外部发电机的评估,而这个外部返回了两个发电机.

(c[k], print(c)) for k in unique_keys)
Run Code Online (Sandbox Code Playgroud)

外部发电机的评估转移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))
...
Run Code Online (Sandbox Code Playgroud)

输出:

# 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]
Run Code Online (Sandbox Code Playgroud)

另一方面,list-comprehension立即进行评估,并且可以获取当前值的值而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)]
Run Code Online (Sandbox Code Playgroud)