繁体   English   中英

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

[英]Generator Comprehension different output from list comprehension?

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

请考虑以下设置:

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

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

因此,当生成器表达式for c in all_configs迭代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的值时,它找到了新值。

要查看发生了什么,请将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也因为执行时拉链vs一次执行)

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

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

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

发生这种情况是因为zip(*)调用导致对外部生成器的评估,并且此外部返回了两个以上的生成器。

(c[k], print(c)) for k in unique_keys)

外生成器的评估将c移动到第二个dict: {'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]

另一方面,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)]

两者都是生成器对象。 第一个是发电机,第二个是发电机中的发电机

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]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM