为什么"if False:"块中的注释分配语句会导致消息"NameError"差异?



如果我在块中注释了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

最新更新