Python 生成器与列表理解冲突
Python generator conflicting with list comprehension
我一直在 Python 中使用生成器函数。我想编写一个函数,它接受一个值为元组的生成器,以及 returns 一个生成器列表,其中每个生成器的值对应于原始元组中的一个索引。
目前,我有一个函数可以为元组中的一定数量的元素完成此操作。这是我的代码:
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!
为什么会这样?我怎样才能达到预期的结果,即参数化将生成器拆分成的块数?
这与 python's lexical scoping 规则有关。用于演示它的经典 "surprising" 示例:
funcs = [ lambda: i for i in range(3) ]
print(funcs[0]())
=> 2 #??
print(funcs[1]())
=> 2 #??
print(funcs[2]())
=> 2
您的示例是相同规则的另一个结果。
要修复,您可以 "break" 使用附加功能的范围:
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
的特定调用的特定值,从而实现所需的行为。没有它,它会绑定 "the current value of the variable named i",它最终会成为您创建的所有生成器的相同值(因为只有一个名为 i
的变量)。
这是因为dropped_copies
是一对迭代器,在对迭代器求值时,i
已经自增1。
尝试使用列表推导,你可以看到不同之处:
dropped_copies = [[x[i] for x in copies[i]] for i in range(2)]
添加到 shx2 的答案中,您也可以用 lambda 替换附加函数:
dropped_copies = [(lambda j: (x[j] for x in copies[j]))(i) for i in range(2)]
这也会在调用 lambda 时创建一个新范围,不同的变量名非常清楚。但是,它也可以使用相同的名称,因为 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]))
请注意,这与原始列表理解版本的损坏方式相同。
我一直在 Python 中使用生成器函数。我想编写一个函数,它接受一个值为元组的生成器,以及 returns 一个生成器列表,其中每个生成器的值对应于原始元组中的一个索引。
目前,我有一个函数可以为元组中的一定数量的元素完成此操作。这是我的代码:
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!
为什么会这样?我怎样才能达到预期的结果,即参数化将生成器拆分成的块数?
这与 python's lexical scoping 规则有关。用于演示它的经典 "surprising" 示例:
funcs = [ lambda: i for i in range(3) ]
print(funcs[0]())
=> 2 #??
print(funcs[1]())
=> 2 #??
print(funcs[2]())
=> 2
您的示例是相同规则的另一个结果。
要修复,您可以 "break" 使用附加功能的范围:
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
的特定调用的特定值,从而实现所需的行为。没有它,它会绑定 "the current value of the variable named i",它最终会成为您创建的所有生成器的相同值(因为只有一个名为 i
的变量)。
这是因为dropped_copies
是一对迭代器,在对迭代器求值时,i
已经自增1。
尝试使用列表推导,你可以看到不同之处:
dropped_copies = [[x[i] for x in copies[i]] for i in range(2)]
添加到 shx2 的答案中,您也可以用 lambda 替换附加函数:
dropped_copies = [(lambda j: (x[j] for x in copies[j]))(i) for i in range(2)]
这也会在调用 lambda 时创建一个新范围,不同的变量名非常清楚。但是,它也可以使用相同的名称,因为 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]))
请注意,这与原始列表理解版本的损坏方式相同。