2015-07-21 54 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])) 

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