Python 变量在生成器中丢失范围?

Python variables lose scope inside generator?

下面的代码returnsNameError: global name 'self' is not defined。为什么?

lengths = [3, 10]
self.fooDict = getOrderedDict(stuff)

if not all(0 < l < len(self.fooDict) for l in lengths):
    raise ValueError("Bad lengths!")

请注意,self.fooDict 是一个包含 35 个条目的 OrderedDict(从集合库中导入)。当我尝试调试时,下面的代码没有错误地执行:

(Pdb) len(self.dataDict)
35
(Pdb) all(0 < size < 35 for size in lengths)
True

但是下面的 debugginf 代码给了我原来的错误:

(Pdb) baz = len(self.dataDict)
(Pdb) all(0 < size < baz for size in lengths)
NameError: global name 'baz' is not defined

简答和解决方法

您 运行 遇到了调试器的限制。输入到调试器中的表达式不能使用 非局部范围的值,因为调试器无法创建所需的闭包。

您可以改为创建 函数 到 运行 您的生成器,从而同时创建一个新范围:

def _test(baz, lengths):
    return all(0 < size < baz for size in lengths)

_test(len(self.dataDict), lengths)

请注意,这也适用于集合和字典推导,在 Python 3 中,列表推导。

长答案,为什么会这样

生成器表达式(以及 set、dict 和 Python 3 列表理解)运行 在新的嵌套命名空间中。生成器表达式中的名称 baz 不是该名称空间中的本地名称,因此 Python 必须在其他地方找到它。 在编译时 Python 确定从何处获取该名称。它将从 编译器 可用的范围进行搜索,如果没有匹配项,则将名称声明为全局名称。

这里用两个生成器表达式来说明:

def function(some_iterable):
    gen1 = (var == spam for var in some_iterable)

    ham = 'bar'
    gen2 = (var == ham for var in some_iterable)

    return gen1, gen2

名称spam在父作用域中未找到,因此编译器将其标记为全局:

>>> dis.dis(function.__code__.co_consts[1])  # gen1
  2           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                17 (to 23)
              6 STORE_FAST               1 (var)
              9 LOAD_FAST                1 (var)
             12 LOAD_GLOBAL              0 (spam)
             15 COMPARE_OP               2 (==)
             18 YIELD_VALUE         
             19 POP_TOP             
             20 JUMP_ABSOLUTE            3
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        

索引 12 处的操作码使用 LOAD_GLOBAL 加载 spam 名称。

名称ham 是在函数范围内找到的,因此编译器生成字节码以从函数中查找名称作为闭包。 同时名字ham被标记为闭包;为 function 生成的代码对变量的处理方式有所不同,因此当函数返回时您仍然可以引用它。

>>> dis.dis(function.__code__.co_consts[3])  # gen2
  4           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                17 (to 23)
              6 STORE_FAST               1 (var)
              9 LOAD_FAST                1 (var)
             12 LOAD_DEREF               0 (ham)
             15 COMPARE_OP               2 (==)
             18 YIELD_VALUE         
             19 POP_TOP             
             20 JUMP_ABSOLUTE            3
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        
>>> function.__code__.co_cellvars  # closure cells
('ham',)

名称 ham 加载了 LOAD_DEREF 操作码,函数代码对象已将该名称列为闭包。当您反汇编 function 时,您会发现除其他字节码外:

>>> dis.dis(function)
  # ....

  4          22 LOAD_CLOSURE             0 (ham)
             25 BUILD_TUPLE              1
             28 LOAD_CONST               3 (<code object <genexpr> at 0x1074a87b0, file "<stdin>", line 4>)
             31 MAKE_CLOSURE             0
             34 LOAD_FAST                0 (some_iterable)
             37 GET_ITER            
             38 CALL_FUNCTION            1
             41 STORE_FAST               2 (gen2)

  # ...

其中 LOAD_CLOSUREMAKE_CLOSURE 字节码为 ham 创建一个闭包,供生成器代码对象使用。

当您在调试器中 运行 任意表达式时,编译器无法访问您正在调试的命名空间。更重要的是,它不能改变那个命名空间来创建一个闭包。因此,在生成器表达式中只能使用 globals