如果我在块中注释了baz = 4
语句if False:
我得到NameError: name 'baz' is not defined
否则我得到的消息NameError: free variable 'baz' referenced before assignment in enclosing scope
我在python中运行Python 3.7.2+
def foo():
def bar():
return baz + 1
if False:
baz = 4
pass
return bar()
foo()
我希望总是得到消息NameError: name 'baz' is not defined
就好像我评论了baz = 4
一样,因为baz = 4
语句从未执行过,但实际消息是NameError: free variable 'baz' referenced before assignment in enclosing scope
变量baz
永远不会被分配,但根据代码,它存在于本地命名空间中。因此,为它保留了一个条目,因此它不是"未定义的",而是"未分配的"。
让我们扩展您的示例:
def foo(b):
def bar(): return baz
if b: baz = 4
return bar, locals()
并使用它:
>>> a, b = foo(0)
>>> c, d = foo(1)
>>> a
<function foo.<locals>.bar at 0x000002320BA6A0D0>
>>> b
{'bar': <function foo.<locals>.bar at 0x000002320BA6A0D0>, 'b': 0}
>>> c
<function foo.<locals>.bar at 0x000002320BA6A268>
>>> d
{'bar': <function foo.<locals>.bar at 0x000002320BA6A268>, 'b': 1, 'baz': 4}
>>> a.__closure__
(<cell at 0x000002320BA3E9A8: empty>,)
>>> c.__closure__
(<cell at 0x000002320BA3E948: int object at 0x000000006E2C6140>,)
>>> a.__closure__[0].cell_contents
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Cell is empty
>>> c.__closure__[0].cell_contents
4
>>> foo.__code__.co_cellvars
('baz',)
在这里,我们看到了区别:在第一种情况下,闭包中的单元格保持为空,在第二种情况下,它包含一个 int 对象(即 4(。
在编译过程中,python 会记下程序使用的变量,而不管条件如何。所以在你的情况下
if False:
baz = 4
pass
在这里,baz将被编译器捕获。
编译器:"嘿,我知道你使用的变量列表。因此,当您尝试调用 bar(( [它使用 baz 变量] 时。编译器将搜索 baz 赋值。它找不到,因为你的"如果"块永远不会执行。所以它扔掉了
NameError: free variable 'baz' referenced before assignment in enclosing s
参考: 极客对于极客 : Python 闭包
谢谢
简而言之
@glglgl得到了关键点
有一个为它保留的条目 [baz],因此它不是"未定义的",而是"未分配的"。
更多详情 ?
从你的问题中,你似乎熟悉闭包的概念,我们不会进一步讨论"为什么它会引发错误",而只会讨论"为什么我们得到两个不同的错误?
在字节码编译期间,python将构建一个符号表,该表本质上是所有作用域的列表,以及其中定义的名称。它用于可变分辨率的运行时。这样做时,python 不关心条件:它只是构建代码具有的所有标识符的列表,并找到它们将被存储在哪个范围内。
因此,此符号表为:
- 考虑代码行数
- 。事件无法到达的线路
- 但不是(显然(评论
一些蟒蛇练习
好吧,你可能会说,你能证明你说的话吗? 下面是一个片段:)
无法访问语句中的定义
- 在
foo
表中,该表将baz
视为局部变量,并且有一个为其分配值的代码路径(它不关心它无法访问的事实( - 在
bar
中,baz
是一个非局部变量,一些代码引用了它
import symtable
code = """
def foo():
def bar():
return baz + 1
if False:
baz = 4
pass
return bar()
"""
table = symtable.symtable(code, '<string>', 'exec')
foo_namespace = table.lookup('foo').get_namespace()
bar_namespace = foo_namespace.lookup('bar').get_namespace()
baz_in_foo = foo_namespace.lookup('baz')
baz_in_bar = bar_namespace.lookup('baz')
print("With unreachable statement 'baz=4'")
print('is_referenced, is_assigned, is_local')
print(baz_in_foo, baz_in_foo.is_referenced(), baz_in_foo.is_assigned(), baz_in_foo.is_local())
print(baz_in_bar, baz_in_bar.is_referenced(), baz_in_bar.is_assigned(), baz_in_bar.is_local())
# With unreachable statement 'baz=4'
# is_referenced, is_assigned, is_local
# baz_in_foo False True True
# baz_in_bar True False False
完全没有定义
- 在
foo
中,表中不存在baz
- 在
bar
中,baz
是一个非局部变量(但Python不知道它可以在哪个范围内找到它(
code = """
import symtable
def foo():
def bar():
return baz + 1
if False:
# baz = 4
pass
return bar()
"""
exec(code)
table = symtable.symtable(code, '<string>', 'exec')
foo_namespace = table.lookup('foo').get_namespace()
bar_namespace = foo_namespace.lookup('bar').get_namespace()
# baz_in_foo = foo_namespace.lookup('baz') # raise KeyError
baz_in_bar = bar_namespace.lookup('baz')
print("Without unreachable statement 'baz=4'")
print('is_referenced, is_assigned, is_local')
print('baz_in_bar', baz_in_bar.is_referenced(), baz_in_bar.is_assigned(), baz_in_bar.is_local())
# Without unreachable statement 'baz=4'
# is_referenced, is_assigned, is_local
# baz_in_bar True False False
来源
https://eli.thegreenplace.net/2011/05/15/understanding-unboundlocalerror-in-python