5

Consider the following:

def f():
    a = 2
    b = [a + i for i in range(3)]
f()

This runs without problems. As I understand it (please correct me if I'm wrong, though), the list comprehension expression introduces a new scope, but since it is created within a function (as opposed to, say, a class), it has access to the surrounding scope, including the variable a.

In contrast, if I were to enter debug mode, stop at line 3 above, and then just manually write the following in the interpreter

>>> b = [a + i for i in range(3)]

I get an error:

Traceback (most recent call last):
  File "<string>", line 293, in runcode
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 1, in <listcomp>
NameError: global name 'a' is not defined

Why is this? When I'm stopped at a given line in debug mode, isn't the scope that I have access to the same as what it would be at runtime?

(I'm using PyScripter, by the way)

andreasdr
  • 3,804
  • 4
  • 28
  • 32

2 Answers2

7

No, you don't quite get the same scope.

Python determines at compile time what variables are looked up in what scope. List comprehensions get their own scope, so names used in the list comprehension are either local to the list comprehension, closures (nonlocal), or globals.

In the function scope, a is a closure to the list comprehension; the compiler knows a is located in the parent scope of f. But if you enter the same expression in an interactive prompt, there is no nested scope because there is no surrounding function being compiled at the same time. As a result, a is assumed by the compiler to be a global instead:

>>> import dis
>>> dis.dis(compile("b = [a + i for i in range(3)]", '<stdin>', 'single').co_consts[0])
  1           0 BUILD_LIST               0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                16 (to 25)
              9 STORE_FAST               1 (i)
             12 LOAD_GLOBAL              0 (a)
             15 LOAD_FAST                1 (i)
             18 BINARY_ADD
             19 LIST_APPEND              2
             22 JUMP_ABSOLUTE            6
        >>   25 RETURN_VALUE

A LOAD_GLOBAL bytecode is used for a here (the .0 is the range(3) iterable for the comprehension).

In a function scope however:

>>> def f():
...     a = 2
...     b = [a + i for i in range(3)]
... 
>>> dis.dis(f.__code__.co_consts[2])
  3           0 BUILD_LIST               0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                16 (to 25)
              9 STORE_FAST               1 (i)
             12 LOAD_DEREF               0 (a)
             15 LOAD_FAST                1 (i)
             18 BINARY_ADD
             19 LIST_APPEND              2
             22 JUMP_ABSOLUTE            6
        >>   25 RETURN_VALUE
>>> f.__code__.co_cellvars
('a',)

a is loaded with LOAD_DEREF, loading the first closure (the cell variable named 'a').

When testing a list comprehension like that in an interactive prompt, you'll have to provide your own nested scope; wrap the expression in a function:

>>> def f(a):
...     return [a + i for i in range(3)]
...
>>> f(a)
[2, 3, 4]
Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • @eryksun: see the linked post; a list comprehension in Python 2 doesn't get executed in a new scope; dict and set comprehensions and generator expressions *do* get a new scope. – Martijn Pieters Apr 12 '14 at 17:48
-1

You have running code in the some one place and source code in the other (your editor). When you stop code by the breakpoint it's not place there you really are, but just view of the current code line before executing in your editor. So if you execute some code in console it have it's own scope. In proof you can make some mistakes in the interpreter, but it'll not touch the code executing, no one exeptions, no one code crash.

  • 1
    It is true that trying to change locals in a debugging session has no effect, but that's only because function locals are optimized and the `locals()` dictionary is but a one-way reflection of the local variables. Globals and mutable objects *can* be altered. The problem in the OP is *not* caused by an inability to alter locals. – Martijn Pieters Apr 12 '14 at 16:16
  • 1
    @eryksun: sure, but that won't make it any easier to run a list comprehension in a debug session expecting local names to be looked up. :-P – Martijn Pieters Apr 12 '14 at 17:50