【问题标题】:List comprehensions: different behaviour with respect to scope in debug mode and in normal runtime列表推导:在调试模式和正常运行时范围内的不同行为
【发布时间】:2014-04-13 00:09:52
【问题描述】:

考虑以下几点:

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)

【问题讨论】:

    标签: python python-3.x scope list-comprehension


    【解决方案1】:

    不,你得到的范围并不完全相同。

    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',)
    

    aLOAD_DEREF 一起加载,加载第一个闭包(名为'a' 的单元格变量)。

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

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

    【讨论】:

    • @eryksun:查看链接帖子; Python 2 中的列表推导不会在新范围内执行; dict 和 set 理解和生成器表达式 do 获得一个新的范围。
    【解决方案2】:

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

    【讨论】:

    • 在调试会话中尝试更改局部变量确实没有效果,但这只是因为函数局部变量被优化并且locals() 字典只是局部变量的单向反映。全局变量和可变对象可以改变。 OP 中的问题不是由无法更改本地人造成的。
    • @eryksun:当然,但这不会使在调试会话中运行列表解析变得更容易,期望查找本地名称。 :-P
    猜你喜欢
    • 1970-01-01
    • 2018-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-17
    • 2015-01-28
    相关资源
    最近更新 更多