列表理解中的生成器表达式未按预期工作
Generator expression in list comprehension not working as expected
以下代码产生预期的输出:
# using a list comprehension as the first expression to a list comprehension
>>> l = [[i*2+x for x in j] for i,j in zip([0,1],[range(4),range(4)])]
>>> l[0]
[0, 1, 2, 3]
>>> l[1]
[2, 3, 4, 5]
然而,当我改用生成器表达式时,我得到了不同的结果:
# using a generator expression as the first expression
>>> l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]
>>> list(l[0])
[2, 3, 4, 5]
>>> list(l[1])
[2, 3, 4, 5]
>>> list(l[0])
[]
>>> list(l[1])
[]
>>> l
[<generator object <listcomp>.<genexpr> at 0x7fddfa413ca8>, <generator object <listcomp>.<genexpr> at 0x7fddfa413c50>]
我知道生成器表达式只能使用一次,但是我无法理解为什么在这种情况下我会两次获得相同的列表,尤其是因为生成器对象似乎是唯一的。
我在这里错过了什么?这是在 Python 3.6.5.
上测试的
生成器对象是唯一的,但它们引用 i
和 j
,但列表推导终止(这实际上创建了一个函数范围,就像列表中的生成器表达式一样-理解)。因此,i
和 j
具有值 i == 1
和 j == range(4)
。你甚至可以反省一下:
In [1]: l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]
In [2]: g = l[0]
In [3]: g.gi_frame.f_locals
Out[3]: {'.0': <range_iterator at 0x10e9be960>, 'i': 1}
这与经常发生这种令人惊讶的行为的原因基本相同:
In [4]: fs = [lambda: i for i in range(3)]
In [5]: fs[0]
Out[5]: <function __main__.<listcomp>.<lambda>()>
In [6]: fs[0]()
Out[6]: 2
In [7]: fs[1]()
Out[7]: 2
In [8]: fs[2]()
Out[8]: 2
您可以使用相同的解决方案解决此问题,即创建另一个封闭范围,它将变量本地绑定到不会更改的内容。使用函数(此处为 lambda,但它可以是常规函数)将完美运行:
In [9]: l = [(lambda i, j: (i*2+x for x in j))(i, j) for i,j in zip([0,1],[range(4),range(4)])]
In [10]: list(l[0])
Out[10]: [0, 1, 2, 3]
In [11]: list(l[1])
Out[11]: [2, 3, 4, 5]
不过,也许为了清楚起见,我将使用不同的参数名称来使正在发生的事情更加明显:
In [12]: l = [(lambda a, b: (a*2+x for x in b))(i, j) for i,j in zip([0,1],[range(4),range(4)])]
In [13]: list(l[0])
Out[13]: [0, 1, 2, 3]
In [14]: list(l[1])
Out[14]: [2, 3, 4, 5]
因为 i
在每个生成器表达式 执行 时绑定到 1。生成器表达式不会捕获在 创建时有效的绑定 - 它们使用在执行时有效的绑定。
>>> j = 100000
>>> e = (j for i in range(3))
>>> j = -6
>>> list(e)
[-6, -6, -6]
以下代码产生预期的输出:
# using a list comprehension as the first expression to a list comprehension
>>> l = [[i*2+x for x in j] for i,j in zip([0,1],[range(4),range(4)])]
>>> l[0]
[0, 1, 2, 3]
>>> l[1]
[2, 3, 4, 5]
然而,当我改用生成器表达式时,我得到了不同的结果:
# using a generator expression as the first expression
>>> l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]
>>> list(l[0])
[2, 3, 4, 5]
>>> list(l[1])
[2, 3, 4, 5]
>>> list(l[0])
[]
>>> list(l[1])
[]
>>> l
[<generator object <listcomp>.<genexpr> at 0x7fddfa413ca8>, <generator object <listcomp>.<genexpr> at 0x7fddfa413c50>]
我知道生成器表达式只能使用一次,但是我无法理解为什么在这种情况下我会两次获得相同的列表,尤其是因为生成器对象似乎是唯一的。
我在这里错过了什么?这是在 Python 3.6.5.
上测试的生成器对象是唯一的,但它们引用 i
和 j
,但列表推导终止(这实际上创建了一个函数范围,就像列表中的生成器表达式一样-理解)。因此,i
和 j
具有值 i == 1
和 j == range(4)
。你甚至可以反省一下:
In [1]: l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]
In [2]: g = l[0]
In [3]: g.gi_frame.f_locals
Out[3]: {'.0': <range_iterator at 0x10e9be960>, 'i': 1}
这与经常发生这种令人惊讶的行为的原因基本相同:
In [4]: fs = [lambda: i for i in range(3)]
In [5]: fs[0]
Out[5]: <function __main__.<listcomp>.<lambda>()>
In [6]: fs[0]()
Out[6]: 2
In [7]: fs[1]()
Out[7]: 2
In [8]: fs[2]()
Out[8]: 2
您可以使用相同的解决方案解决此问题,即创建另一个封闭范围,它将变量本地绑定到不会更改的内容。使用函数(此处为 lambda,但它可以是常规函数)将完美运行:
In [9]: l = [(lambda i, j: (i*2+x for x in j))(i, j) for i,j in zip([0,1],[range(4),range(4)])]
In [10]: list(l[0])
Out[10]: [0, 1, 2, 3]
In [11]: list(l[1])
Out[11]: [2, 3, 4, 5]
不过,也许为了清楚起见,我将使用不同的参数名称来使正在发生的事情更加明显:
In [12]: l = [(lambda a, b: (a*2+x for x in b))(i, j) for i,j in zip([0,1],[range(4),range(4)])]
In [13]: list(l[0])
Out[13]: [0, 1, 2, 3]
In [14]: list(l[1])
Out[14]: [2, 3, 4, 5]
因为 i
在每个生成器表达式 执行 时绑定到 1。生成器表达式不会捕获在 创建时有效的绑定 - 它们使用在执行时有效的绑定。
>>> j = 100000
>>> e = (j for i in range(3))
>>> j = -6
>>> list(e)
[-6, -6, -6]