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_CLOSURE
和 MAKE_CLOSURE
字节码为 ham
创建一个闭包,供生成器代码对象使用。
当您在调试器中 运行 任意表达式时,编译器无法访问您正在调试的命名空间。更重要的是,它不能改变那个命名空间来创建一个闭包。因此,在生成器表达式中只能使用 globals。
下面的代码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_CLOSURE
和 MAKE_CLOSURE
字节码为 ham
创建一个闭包,供生成器代码对象使用。
当您在调试器中 运行 任意表达式时,编译器无法访问您正在调试的命名空间。更重要的是,它不能改变那个命名空间来创建一个闭包。因此,在生成器表达式中只能使用 globals。