Python 列表理解:在调试模式和正常运行时,作用域的不同行为

Python 列表理解:在调试模式和正常运行时,作用域的不同行为,python,python-3.x,scope,list-comprehension,Python,Python 3.x,Scope,List Comprehension,考虑以下几点: def f(): a = 2 b = [a + i for i in range(3)] f() 这运行没有问题。据我所知(如果我错了,请纠正我),列表理解表达式引入了一个新的作用域,但由于它是在函数(而不是类)中创建的,因此它可以访问周围的作用域,包括变量a 相反,如果要进入调试模式,请在上面的第3行停止,然后在解释器中手动编写以下内容 >>> b = [a + i for i in range(3)] 我得到一个错误: Traceback

考虑以下几点:

def f():
    a = 2
    b = [a + i for i in range(3)]
f()
这运行没有问题。据我所知(如果我错了,请纠正我),列表理解表达式引入了一个新的作用域,但由于它是在函数(而不是类)中创建的,因此它可以访问周围的作用域,包括变量
a

相反,如果要进入调试模式,请在上面的第3行停止,然后在解释器中手动编写以下内容

>>> b = [a + i for i in range(3)]
我得到一个错误:

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
回溯(最近一次呼叫最后一次):
文件“”,第293行,运行代码
文件“”,第1行,在
文件“”,第1行,在
NameError:未定义全局名称“a”
为什么会这样?当我在调试模式下停在给定行时,我可以访问的范围是否与运行时的范围相同


(顺便说一句,我使用的是PyScripter)

不,您的作用域并不完全相同

Python在编译时确定在什么范围内查找哪些变量,因此,列表理解中使用的名称要么是列表理解的本地名称,要么是闭包(非本地名称),要么是全局名称

在功能范围内,
a
是列表理解的结束;编译器知道
a
位于
f
的父范围内。但是,如果在交互提示中输入相同的表达式,则不会有嵌套的作用域,因为不会同时编译周围的函数。因此,
a
被编译器假定为全局的:

>>> 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\u DEREF
,加载第一个闭包(名为
'a'
的单元格变量)

在交互式提示中测试列表理解时,必须提供自己的嵌套范围;将表达式包装到函数中:

>>> def f(a):
...     return [a + i for i in range(3)]
...
>>> f(a)
[2, 3, 4]

在某个地方运行代码,在另一个地方(编辑器)运行源代码。当您通过断点停止代码时,它并不是您真正的位置,而是在编辑器中执行之前查看当前代码行。所以,若您在控制台中执行一些代码,它就有自己的作用域。事实证明,您可能会在解释器中犯一些错误,但它不会影响代码的执行,不会出现任何异常,也不会出现任何代码崩溃。

在调试会话中尝试更改局部变量确实没有效果,但这只是因为函数局部变量已优化,并且
局部变量()
字典只是局部变量的单向反映。全局对象和可变对象可以更改。OP中的问题不是由于无法更改局部变量造成的。@eryksun:当然,但这不会使在需要查找局部名称的调试会话中运行列表理解变得更容易-P@eryksun:参见链接的帖子;Python 2中的列表理解不会在新的范围内执行;dict和set理解和生成器表达式确实有了一个新的范围。
>>> def f(a):
...     return [a + i for i in range(3)]
...
>>> f(a)
[2, 3, 4]