Python变量在发生器内失去范围?

时间:2015-07-08 16:27:03

标签: python scope generator

以下代码返回NameError: 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是一个OrderedDict(从集合库导入),有35个条目。当我尝试调试时,下面的代码执行时没有错误:

(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

1 个答案:

答案 0 :(得分:11)

简短回答和解决方法

您遇到了调试器的限制。输入调试器的表达式不能使用非本地作用域值,因为调试器无法创建所需的闭包。

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

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 之外,你不能在生成器表达式中使用任何东西。