列表(生成器)的意外输出
Unexpected output from list(generator)
我有一个列表和一个 lambda
函数定义为
In [1]: i = lambda x: a[x]
In [2]: alist = [(1, 2), (3, 4)]
然后我尝试了两种不同的方法来计算一个简单的总和
第一种方法。
In [3]: [i(0) + i(1) for a in alist]
Out[3]: [3, 7]
第二种方法。
In [4]: list(i(0) + i(1) for a in alist)
Out[4]: [7, 7]
两个结果出乎意料的不同。为什么会这样?
此行为已在 python 中得到修复 3. 当您使用列表理解 [i(0) + i(1) for a in alist]
时,您将在其周围范围内定义 a
,i
可以访问该范围.在新会话中 list(i(0) + i(1) for a in alist)
会抛出错误。
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> list(i(0) + i(1) for a in alist)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
File "<stdin>", line 1, in <lambda>
NameError: global name 'a' is not defined
列表理解不是生成器:Generator expressions and list comprehensions。
Generator expressions are surrounded by parentheses (“()”) and list
comprehensions are surrounded by square brackets (“[]”).
在你的例子中 list()
作为一个 class 有它自己的变量范围并且它最多可以访问全局变量。当您使用它时,i
将在该范围内查找 a
。在新会话中试试这个:
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> [i(0) + i(1) for a in alist]
[3, 7]
>>> a
(3, 4)
在另一个会话中将其与此进行比较:
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> l = (i(0) + i(1) for a in alist)
<generator object <genexpr> at 0x10e60db90>
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> [x for x in l]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
File "<stdin>", line 1, in <lambda>
NameError: global name 'a' is not defined
当您 运行 list(i(0) + i(1) for a in alist)
时,您会将生成器 (i(0) + i(1) for a in alist)
传递给 list
class,它将尝试将其转换为列表在 return 列表之前它自己的范围。对于这个无法访问 lambda 函数内部的生成器,变量 a
没有任何意义。
生成器对象 <generator object <genexpr> at 0x10e60db90>
丢失了变量名 a
。然后当 list
尝试调用生成器时,lambda 函数将针对未定义的 a
抛出错误。
列表理解与生成器对比的行为也提到了here:
List comprehensions also "leak" their loop variable into the
surrounding scope. This will also change in Python 3.0, so that the
semantic definition of a list comprehension in Python 3.0 will be
equivalent to list(). Python 2.4 and beyond
should issue a deprecation warning if a list comprehension's loop
variable has the same name as a variable used in the immediately
surrounding scope.
在python3:
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> [i(0) + i(1) for a in alist]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
File "<stdin>", line 1, in <lambda>
NameError: name 'a' is not defined
a
在全局范围内。
所以它应该给出错误
解决方案是:
i = lambda a, x: a[x]
您应该将 a
作为您的 lambda 函数的参数。这按预期工作:
In [10]: alist = [(1, 2), (3, 4)]
In [11]: i = lambda a, x: a[x]
In [12]: [i(a, 0) + i(a, 1) for a in alist]
Out[12]: [3, 7]
In [13]: list(i(a, 0) + i(a, 1) for a in alist)
Out[13]: [3, 7]
获得相同结果的另一种方法是:
In [14]: [sum(a) for a in alist]
Out[14]: [3, 7]
EDIT 这个答案只是一个简单的解决方法,并不是问题的真正答案。观察到的效果有点复杂,看我的.
执行[i(0) + i(1) for a in alist]
后,a
变为(3,4)
。
然后当下面一行被执行时:
list(i(0) + i(1) for a in alist)
(3,4)
值被 lambda 函数 i
两次用作 a
的值,因此它打印 [7,7].
相反,您应该定义具有两个参数 a
和 x
的 lambda 函数。
i = lambda a,x : a[x]
这里需要了解的重要事项是
生成器表达式将在内部创建函数对象,但列表理解不会。
它们都将循环变量绑定到值,如果循环变量尚未创建,则它们将在当前范围内。
让我们看看生成器表达式的字节码
>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec'))
1 0 LOAD_CONST 0 (<code object <genexpr> at ...>)
3 MAKE_FUNCTION 0
6 LOAD_NAME 0 (alist)
9 GET_ITER
10 CALL_FUNCTION 1
13 POP_TOP
14 LOAD_CONST 1 (None)
17 RETURN_VALUE
它加载代码对象,然后使它成为一个函数。让我们看看实际的代码对象。
>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 27 (to 33)
6 STORE_FAST 1 (a)
9 LOAD_GLOBAL 0 (i)
12 LOAD_CONST 0 (0)
15 CALL_FUNCTION 1
18 LOAD_GLOBAL 0 (i)
21 LOAD_CONST 1 (1)
24 CALL_FUNCTION 1
27 BINARY_ADD
28 YIELD_VALUE
29 POP_TOP
30 JUMP_ABSOLUTE 3
>> 33 LOAD_CONST 2 (None)
36 RETURN_VALUE
正如您在此处看到的,迭代器的当前值存储在变量 a
中。但是由于我们将其设为函数对象,因此创建的 a
将仅在生成器表达式中可见。
但是在列表理解的情况下,
>>> dis(compile('[i(0) + i(1) for a in alist]', 'string', 'exec'))
1 0 BUILD_LIST 0
3 LOAD_NAME 0 (alist)
6 GET_ITER
>> 7 FOR_ITER 28 (to 38)
10 STORE_NAME 1 (a)
13 LOAD_NAME 2 (i)
16 LOAD_CONST 0 (0)
19 CALL_FUNCTION 1
22 LOAD_NAME 2 (i)
25 LOAD_CONST 1 (1)
28 CALL_FUNCTION 1
31 BINARY_ADD
32 LIST_APPEND 2
35 JUMP_ABSOLUTE 7
>> 38 POP_TOP
39 LOAD_CONST 2 (None)
42 RETURN_VALUE
没有明确的函数创建,变量a
是在当前范围内创建的。因此,a
泄漏到当前范围。
有了这个理解,让我们来解决你的问题。
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
现在,当您使用理解创建列表时,
>>> [i(0) + i(1) for a in alist]
[3, 7]
>>> a
(3, 4)
您可以看到 a
已泄漏到当前范围,并且它仍然绑定到迭代中的最后一个值。
因此,当您在列表理解后迭代生成器表达式时,lambda
函数使用泄漏的 a
。这就是为什么你得到 [7, 7]
,因为 a
仍然绑定到 (3, 4)
。
但是,如果您首先迭代生成器表达式,那么 a
将绑定到 alist
的值,并且不会泄漏到当前范围,因为生成器表达式成为一个函数。因此,当 lambda
函数尝试访问 a
时,它无法在任何地方找到它。这就是它因错误而失败的原因。
注意: 在 Python 3.x 中无法观察到相同的行为,因为通过为列表理解创建函数也可以防止泄漏。您可能想在 Guido 自己撰写的 Python 博客的 post、From List Comprehensions to Generator Expressions 的历史中阅读更多相关信息。
请参阅我的其他答案以了解解决方法。但是仔细想想,问题似乎有点复杂。我认为这里有几个问题:
当你做i = lambda x: a[x]
时,变量a
不是参数
对于函数,这称为
closure。这对于 lambda 表达式和普通函数定义都是相同的。
Python 显然是 'late binding',这意味着您关闭的变量的值只会在您调用函数时查找。这会导致 various unexpected results.
在 Python 2 中,列表推导式会泄漏其循环变量,而生成器表达式不会泄漏循环变量(请参阅 this PEP 了解详情)。此差异已在 Python 3 中删除,其中列表理解是 list(generater_expression)
的快捷方式。我不确定,但这可能意味着 Python2 列表理解在它们的外部范围内执行,而生成器表达式和 Python3 列表理解创建它们自己的内部范围。
演示(在 Python2 中):
In [1]: def f(): # closes over a from global scope
...: return 2 * a
...:
In [2]: list(f() for a in range(5)) # does not find a in global scope
[...]
NameError: global name 'a' is not defined
In [3]: [f() for a in range(5)]
# executes in global scope, so f finds a. Also leaks a=8
Out[3]: [0, 2, 4, 6, 8]
In [4]: list(f() for a in range(5)) # finds a=8 in global scope
Out[4]: [8, 8, 8, 8, 8]
在Python3:
In [1]: def f():
...: return 2 * a
...:
In [2]: list(f() for a in range(5))
# does not find a in global scope, does not leak a
[...]
NameError: name 'a' is not defined
In [3]: [f() for a in range(5)]
# does not find a in global scope, does not leak a
[...]
NameError: name 'a' is not defined
In [4]: list(f() for a in range(5)) # a still undefined
[...]
NameError: name 'a' is not defined
我有一个列表和一个 lambda
函数定义为
In [1]: i = lambda x: a[x]
In [2]: alist = [(1, 2), (3, 4)]
然后我尝试了两种不同的方法来计算一个简单的总和
第一种方法。
In [3]: [i(0) + i(1) for a in alist]
Out[3]: [3, 7]
第二种方法。
In [4]: list(i(0) + i(1) for a in alist)
Out[4]: [7, 7]
两个结果出乎意料的不同。为什么会这样?
此行为已在 python 中得到修复 3. 当您使用列表理解 [i(0) + i(1) for a in alist]
时,您将在其周围范围内定义 a
,i
可以访问该范围.在新会话中 list(i(0) + i(1) for a in alist)
会抛出错误。
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> list(i(0) + i(1) for a in alist)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
File "<stdin>", line 1, in <lambda>
NameError: global name 'a' is not defined
列表理解不是生成器:Generator expressions and list comprehensions。
Generator expressions are surrounded by parentheses (“()”) and list comprehensions are surrounded by square brackets (“[]”).
在你的例子中 list()
作为一个 class 有它自己的变量范围并且它最多可以访问全局变量。当您使用它时,i
将在该范围内查找 a
。在新会话中试试这个:
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> [i(0) + i(1) for a in alist]
[3, 7]
>>> a
(3, 4)
在另一个会话中将其与此进行比较:
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> l = (i(0) + i(1) for a in alist)
<generator object <genexpr> at 0x10e60db90>
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> [x for x in l]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
File "<stdin>", line 1, in <lambda>
NameError: global name 'a' is not defined
当您 运行 list(i(0) + i(1) for a in alist)
时,您会将生成器 (i(0) + i(1) for a in alist)
传递给 list
class,它将尝试将其转换为列表在 return 列表之前它自己的范围。对于这个无法访问 lambda 函数内部的生成器,变量 a
没有任何意义。
生成器对象 <generator object <genexpr> at 0x10e60db90>
丢失了变量名 a
。然后当 list
尝试调用生成器时,lambda 函数将针对未定义的 a
抛出错误。
列表理解与生成器对比的行为也提到了here:
List comprehensions also "leak" their loop variable into the surrounding scope. This will also change in Python 3.0, so that the semantic definition of a list comprehension in Python 3.0 will be equivalent to list(). Python 2.4 and beyond should issue a deprecation warning if a list comprehension's loop variable has the same name as a variable used in the immediately surrounding scope.
在python3:
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
>>> [i(0) + i(1) for a in alist]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
File "<stdin>", line 1, in <lambda>
NameError: name 'a' is not defined
a
在全局范围内。
所以它应该给出错误
解决方案是:
i = lambda a, x: a[x]
您应该将 a
作为您的 lambda 函数的参数。这按预期工作:
In [10]: alist = [(1, 2), (3, 4)]
In [11]: i = lambda a, x: a[x]
In [12]: [i(a, 0) + i(a, 1) for a in alist]
Out[12]: [3, 7]
In [13]: list(i(a, 0) + i(a, 1) for a in alist)
Out[13]: [3, 7]
获得相同结果的另一种方法是:
In [14]: [sum(a) for a in alist]
Out[14]: [3, 7]
EDIT 这个答案只是一个简单的解决方法,并不是问题的真正答案。观察到的效果有点复杂,看我的
执行[i(0) + i(1) for a in alist]
后,a
变为(3,4)
。
然后当下面一行被执行时:
list(i(0) + i(1) for a in alist)
(3,4)
值被 lambda 函数 i
两次用作 a
的值,因此它打印 [7,7].
相反,您应该定义具有两个参数 a
和 x
的 lambda 函数。
i = lambda a,x : a[x]
这里需要了解的重要事项是
生成器表达式将在内部创建函数对象,但列表理解不会。
它们都将循环变量绑定到值,如果循环变量尚未创建,则它们将在当前范围内。
让我们看看生成器表达式的字节码
>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec'))
1 0 LOAD_CONST 0 (<code object <genexpr> at ...>)
3 MAKE_FUNCTION 0
6 LOAD_NAME 0 (alist)
9 GET_ITER
10 CALL_FUNCTION 1
13 POP_TOP
14 LOAD_CONST 1 (None)
17 RETURN_VALUE
它加载代码对象,然后使它成为一个函数。让我们看看实际的代码对象。
>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 27 (to 33)
6 STORE_FAST 1 (a)
9 LOAD_GLOBAL 0 (i)
12 LOAD_CONST 0 (0)
15 CALL_FUNCTION 1
18 LOAD_GLOBAL 0 (i)
21 LOAD_CONST 1 (1)
24 CALL_FUNCTION 1
27 BINARY_ADD
28 YIELD_VALUE
29 POP_TOP
30 JUMP_ABSOLUTE 3
>> 33 LOAD_CONST 2 (None)
36 RETURN_VALUE
正如您在此处看到的,迭代器的当前值存储在变量 a
中。但是由于我们将其设为函数对象,因此创建的 a
将仅在生成器表达式中可见。
但是在列表理解的情况下,
>>> dis(compile('[i(0) + i(1) for a in alist]', 'string', 'exec'))
1 0 BUILD_LIST 0
3 LOAD_NAME 0 (alist)
6 GET_ITER
>> 7 FOR_ITER 28 (to 38)
10 STORE_NAME 1 (a)
13 LOAD_NAME 2 (i)
16 LOAD_CONST 0 (0)
19 CALL_FUNCTION 1
22 LOAD_NAME 2 (i)
25 LOAD_CONST 1 (1)
28 CALL_FUNCTION 1
31 BINARY_ADD
32 LIST_APPEND 2
35 JUMP_ABSOLUTE 7
>> 38 POP_TOP
39 LOAD_CONST 2 (None)
42 RETURN_VALUE
没有明确的函数创建,变量a
是在当前范围内创建的。因此,a
泄漏到当前范围。
有了这个理解,让我们来解决你的问题。
>>> i = lambda x: a[x]
>>> alist = [(1, 2), (3, 4)]
现在,当您使用理解创建列表时,
>>> [i(0) + i(1) for a in alist]
[3, 7]
>>> a
(3, 4)
您可以看到 a
已泄漏到当前范围,并且它仍然绑定到迭代中的最后一个值。
因此,当您在列表理解后迭代生成器表达式时,lambda
函数使用泄漏的 a
。这就是为什么你得到 [7, 7]
,因为 a
仍然绑定到 (3, 4)
。
但是,如果您首先迭代生成器表达式,那么 a
将绑定到 alist
的值,并且不会泄漏到当前范围,因为生成器表达式成为一个函数。因此,当 lambda
函数尝试访问 a
时,它无法在任何地方找到它。这就是它因错误而失败的原因。
注意: 在 Python 3.x 中无法观察到相同的行为,因为通过为列表理解创建函数也可以防止泄漏。您可能想在 Guido 自己撰写的 Python 博客的 post、From List Comprehensions to Generator Expressions 的历史中阅读更多相关信息。
请参阅我的其他答案以了解解决方法。但是仔细想想,问题似乎有点复杂。我认为这里有几个问题:
当你做
i = lambda x: a[x]
时,变量a
不是参数 对于函数,这称为 closure。这对于 lambda 表达式和普通函数定义都是相同的。Python 显然是 'late binding',这意味着您关闭的变量的值只会在您调用函数时查找。这会导致 various unexpected results.
在 Python 2 中,列表推导式会泄漏其循环变量,而生成器表达式不会泄漏循环变量(请参阅 this PEP 了解详情)。此差异已在 Python 3 中删除,其中列表理解是
list(generater_expression)
的快捷方式。我不确定,但这可能意味着 Python2 列表理解在它们的外部范围内执行,而生成器表达式和 Python3 列表理解创建它们自己的内部范围。
演示(在 Python2 中):
In [1]: def f(): # closes over a from global scope
...: return 2 * a
...:
In [2]: list(f() for a in range(5)) # does not find a in global scope
[...]
NameError: global name 'a' is not defined
In [3]: [f() for a in range(5)]
# executes in global scope, so f finds a. Also leaks a=8
Out[3]: [0, 2, 4, 6, 8]
In [4]: list(f() for a in range(5)) # finds a=8 in global scope
Out[4]: [8, 8, 8, 8, 8]
在Python3:
In [1]: def f():
...: return 2 * a
...:
In [2]: list(f() for a in range(5))
# does not find a in global scope, does not leak a
[...]
NameError: name 'a' is not defined
In [3]: [f() for a in range(5)]
# does not find a in global scope, does not leak a
[...]
NameError: name 'a' is not defined
In [4]: list(f() for a in range(5)) # a still undefined
[...]
NameError: name 'a' is not defined