簡體   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