列表(生成器)的意外输出

时间:2015-07-04 08:55:54

标签: python python-2.7 list-comprehension generator-expression

我有一个列表,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]

两种结果都出乎意料地不同。为什么会这样?

6 个答案:

答案 0 :(得分:15)

此行为已在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

  

生成器表达式用括号(“()”)和列表括起来   理解用方括号(“[]”)包围。

在您的示例list()中,因为类具有自己的变量范围,并且最多可以访问全局变量。当您使用它时,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类,它会在返回列表之前尝试将其转换为自己范围内的列表。对于lambda函数内无法访问的生成器,变量a没有意义。

生成器对象<generator object <genexpr> at 0x10e60db90>已丢失变量名a。然后当list尝试调用生成器时,lambda函数将为未定义的a抛出错误。

与生成器相比,列表推导的行为也提到here

  

列表推导也将其循环变量“泄漏”到   周围范围。这也将在Python 3.0中发生变化,以便   Python 3.0中列表理解的语义定义将是   相当于list()。 Python 2.4及更高版本   如果列表理解的循环应该发出弃用警告   变量与立即使用的变量同名   周围范围。

在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

答案 1 :(得分:5)

你应该为你的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]

获得相同结果的另一种方法是:

SET MYROW_ID = (SELECT id FROM withdrawals ORDER BY lastmodified DESC LIMIT 1);

编辑这个答案只是一个简单的解决方法,并不是这个问题的真正答案。观察到的效果有点复杂,请参阅我的other answer

答案 2 :(得分:5)

这里需要了解的重要事项是

  1. 生成器表达式将在内部创建函数对象,但列表理解不会。

  2. 它们都将循环变量绑定到值,如果尚未创建循环变量,则它们将在当前范围内。

  3. 让我们看一下生成器表达式的字节码

    >>> 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历史博客帖子From List Comprehensions to Generator Expressions中阅读更多相关内容。

答案 3 :(得分:2)

请参阅我的其他答案以获得解决方法。但是想一想,问题似乎有点复杂。我认为这里有几个问题:

  • 执行i = lambda x: a[x]时,变量a不是参数 这个函数叫做a closure。对于lambda表达式和普通函数定义,这都是相同的。

  • Python显然会进行后期绑定&#39;,这意味着您关闭的变量的值只会在您调用函数时查找。这可能导致various意外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

答案 4 :(得分:1)

a属于全球范围。 所以它应该给出错误

解决方案是:

i = lambda a, x: a[x]

答案 5 :(得分:1)

执行[i(0) + i(1) for a in alist]后,a变为(3,4)

然后执行以下行:

list(i(0) + i(1) for a in alist)
lambda函数(3,4)使用

i值作为a的值,因此它打印[7,7].

相反,您应该定义具有两个参数ax的lambda函数。

i = lambda a,x : a[x]