Search code examples
pythonpython-2.7list-comprehensiongenerator-expression

Unexpected output from list(generator)


I have a list and a lambda function defined as

In [1]: i = lambda x: a[x]
In [2]: alist = [(1, 2), (3, 4)]

Then I try two different methods to calculate a simple sum

First method.

In [3]: [i(0) + i(1) for a in alist]
Out[3]: [3, 7]

Second method.

In [4]: list(i(0) + i(1) for a in alist)
Out[4]: [7, 7]

Both results are unexpectedly different. Why is that happening?


Solution

  • This behaviour has been fixed in python 3. When you use a list comprehension [i(0) + i(1) for a in alist] you will define a in its surrounding scope which is accessible for i. In a new session list(i(0) + i(1) for a in alist) will throw error.

    >>> 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
    

    A list comprehension is not a generator: Generator expressions and list comprehensions.

    Generator expressions are surrounded by parentheses (“()”) and list comprehensions are surrounded by square brackets (“[]”).

    In your example list() as a class has its own scope of variables and it has access to global variables at most. When you use that, i will look for a inside that scope. Try this in new session:

    >>> i = lambda x: a[x]
    >>> alist = [(1, 2), (3, 4)]
    >>> [i(0) + i(1) for a in alist]
    [3, 7]
    >>> a
    (3, 4)
    

    Compare it to this in another session:

    >>> 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
    

    When you run list(i(0) + i(1) for a in alist) you will pass a generator (i(0) + i(1) for a in alist) to the list class which it will try to convert it to a list in its own scope before return the list. For this generator which has no access inside lambda function, the variable a has no meaning.

    The generator object <generator object <genexpr> at 0x10e60db90> has lost the variable name a. Then when list tries to call the generator, lambda function will throw error for undefined a.

    The behaviour of list comprehensions in contrast with generators also mentioned 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.

    In 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