List comprehensions: different behaviour with respect to scope in debug mode and in normal runtime

落爺英雄遲暮 提交于 2021-01-27 05:40:15

问题


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)


回答1:


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]



回答2:


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.



来源:https://stackoverflow.com/questions/23032591/list-comprehensions-different-behaviour-with-respect-to-scope-in-debug-mode-and

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!