2015-07-21 23 views
4

我一直在用生成器函數搞亂Python。我想寫一個函數,它使用一個其值爲元組的生成器,並返回一個生成器列表,其中每個生成器的值對應於原始元組中的一個索引。Python生成器與列表理解衝突

目前,我有一個函數爲元組中的硬編碼元素實現此功能。這裏是我的代碼:

import itertools 

def tee_pieces(generator): 
    copies = itertools.tee(generator) 
    dropped_copies = [(x[0] for x in copies[0]), (x[1] for x in copies[1])] 
    # dropped_copies = [(x[i] for x in copies[i]) for i in range(2)] 
    return dropped_copies 

def gen_words(): 
    for i in "Hello, my name is Fred!".split(): 
     yield i 

def split_words(words): 
    for word in words: 
     yield (word[:len(word)//2], word[len(word)//2:]) 

def print_words(words): 
    for word in words: 
     print(word) 

init_words = gen_words() 
right_left_words = split_words(init_words) 
left_words, right_words = tee_pieces(right_left_words) 
print("Left halves:") 
print_words(left_words) 
print("Right halves:") 
print_words(right_words) 

這正確地分裂發生器,導致left_words包含左半部分和right_words包含右半部分。

問題出現在我嘗試使用上面註釋過的行參數化要創建的發電機數量時。據我所知,應該是等價的,但是當我使用該行代替,既left_words和right_words最終方含字的右半邊,給人這樣的輸出:

Left halves: 
lo, 
y 
me 
s 
ed! 
Right halves: 
lo, 
y 
me 
s 
ed! 

這究竟是爲什麼?我怎樣才能適應期望的結果,即參數化將發生器分成幾部分?

回答

3

這與python's lexical scoping規則有關。用於演示的經典「令人驚訝」的示例:

funcs = [ lambda: i for i in range(3) ] 
print(funcs[0]()) 
=> 2 #?? 
print(funcs[1]()) 
=> 2 #?? 
print(funcs[2]()) 
=> 2 

您的示例是具有相同規則的另一結果。

要解決,你可以在「破」的範圍界定與附加功能:

def make_gen(i): 
    return (x[i] for x in copies[i]) 
dropped_copies = [make_gen(i) for i in range(2)] 

這種綁定的i傳遞給特定的呼叫make_gen具體值,達到所期望的行爲的價值。如果沒有它,它將綁定「變量名爲i的當前值」,它將以您創建的所有生成器的相同值(因爲只有一個名爲i的變量)結束。

+1

謝謝,這很好地解決了它。 – isaacg

0

這是因爲dropped_copies是一對迭代器,並在迭代器進行評估,i已經遞增到1

嘗試使用列表理解,你可以看到其中的差別:

dropped_copies = [[x[i] for x in copies[i]] for i in range(2)] 
2

太添加到shx2的回答,您還可以通過一個lambda替代附加功能:

dropped_copies = [(lambda j: (x[j] for x in copies[j]))(i) for i in range(2)] 

這太科瑞當lambda被調用時,它是一個新的作用域,這一點可以通過不同的變量名清楚地說明。它將但是也可以使用相同名稱的工作,因爲拉姆達內部的參數陰影發電機內部的一個:

dropped_copies = [(lambda i: (x[i] for x in copies[i]))(i) for i in range(2)] 

這類作用域看起來很混亂,但如果你重寫發電機變得更直觀作爲一個for循環:

dropped_copies = [] 
for i in range(2): 
    dropped_copies.append((x[i] for x in copies[i])) 

注意,這是相同的方式原始列表理解的版本是壞。