列表推导:在调试模式和正常运行时中与范围不同的行为

时间:2014-04-12 15:46:44

标签: 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 (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

这是为什么?当我在调试模式下停在给定的行时,是否可以访问与运行时相同的范围?

(顺便说一句,我正在使用PyScripter)

2 个答案:

答案 0 :(得分:6)

不,你没有达到相同的范围。

Python在编译时确定 在什么范围内查找哪些变量。 List comprehensions get their own scope,因此列表推导中使用的名称是列表推导的本地,闭包(非本地)或全局变量。

在函数范围中,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

此处LOAD_GLOBAL使用a字节码(.0range(3)可迭代的理解符。)

但在功能范围内:

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

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

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

答案 1 :(得分:-1)

您在某个地方运行代码,在另一个地方运行源代码(您的编辑器)。当您通过断点停止代码时,它并不是您真正存在的,而只是在编辑器中执行之前查看当前代码行。因此,如果您在控制台中执行某些代码,它就拥有自己的范围。在证明你可以在解释器中犯一些错误,但它不会触及代码执行,没有一个exeptions,没有一个代码崩溃。