def fun():
if False:
x=3
print(locals())
print(x)
fun()
输出和错误消息:
{}
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
in ()
4 print(locals())
5 print(x)
----> 6 fun()
in fun()
3 x=3
4 print(locals())
----> 5 print(x)
6 fun()
UnboundLocalError: local variable 'x' referenced before assignment
我想知道python解释器是如何工作的。 请注意,x = 3根本不运行,并且不应将其视为局部变量,这意味着错误将是"名称'x'未定义"。 但是查看代码和错误消息,情况并非如此。 谁能解释一下这种情况背后的python解释器编译的机制原理?
可能重复stackoverflow.com/q/7969949/3758972
这可能是相关的:范围规则的简短描述
如果下面的答案之一解决了您的问题,您应该接受它(单击相应答案旁边的复选标记)。 这样做有两件事。 它让每个人都知道您的问题已得到解决,让您满意,并且它可以帮助您获得帮助。 请参阅此处以获取完整说明。
因此,Python将始终将每个函数中的每个名称分类为本地名称,非本地名称或全局名称。这些名称范围是独家的;在每个函数中(嵌套函数中的名称都有自己的命名范围),每个名称只能属于这些类别中的一个。
当Python编译这段代码时:
def fun():
if False:
x=3
它将产生一个抽象语法树,如:
FunctionDef(
name='fun',
args=arguments(...), b
body=[
If(test=NameConstant(value=False),
body=[
Assign(targets=[Name(id='x', ctx=Store())], value=Num(n=3))
],
orelse=[])
]
)
(为简洁起见省略了一些东西)。现在,当这个抽象语法树被编译成代码时,Python将扫描所有名称节点。如果有Name的任何Name节点,则该名称被认为是封闭的FunctionDef的本地名称(如果有),除非用global(即global x)或nonlocal(nonlocal x)覆盖)同一函数定义中的语句。
ctx=Store()主要发生在有问题的名称在赋值的左侧使用,或者作为for循环中的迭代变量时。
现在,当Python将其编译为字节码时,生成的字节码就是
>>> dis.dis(fun)
4 0 LOAD_GLOBAL 0 (print)
3 LOAD_FAST 0 (x)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
优化器完全删除了if语句;但是由于变量已经标记为函数的局部变量,LOAD_FAST用于x,这将导致从局部变量和局部变量访问x。由于尚未设置x,因此抛出UnboundLocalError。另一方面,名称print从未被赋值,因此被认为是此函数中的全局名称,因此其值加载LOAD_GLOBAL。
函数中使用的名称只能包含整个函数体的一个范围。范围在编译时确定(而不是在运行函数时)。
如果在函数中的任何位置都有对名称的赋值(无论是否在调用函数时运行它),编译器都会默认将该名称视为函数的本地名称。您可以使用global和nonlocal语句明确告诉它使用不同的范围。
一种特殊情况是在一个函数体中分配名称,并从第一个函数中定义的另一个函数访问。这样的变量将放在一个特殊的closure单元格中,该单元格将在函数之间共享。外部函数将变量视为局部变量,而内部函数只能在其具有名称的nonlocal语句时才分配给它。这是一个闭包和nonlocal语句的示例:
def counter():
val = 0
def helper():
nonlocal val
val += 1
return val
return helper
除了您所看到的问题之外,您可能会看到另一种范围混淆:
x = 1
def foo():
print(x) # you might expect this to print the global x, but it raises an exception
x = 2 # this assignment makes the compiler treat the name x as local to the function
在foo函数中,名称x在任何地方都被视为本地,即使print调用在将其分配到本地名称空间之前尝试使用它。
谢谢你提到关闭。
x = 3无法访问的事实无关紧要。该函数分配给它,因此它必须是本地名称。
请记住,在执行开始之前编译整个文件,但是在执行阶段定义函数,当执行编译的函数定义块时,创建函数对象。
复杂的优化器可以消除无法访问的代码,但CPython的优化器并不那么聪明 - 它只执行非常简单的锁孔优化。 s>
要深入了解Python内部,请查看ast和dis模块。
你能详细说明一下吗?
The fact that x = 3 is unreachable is irrelevant. The function assigns to it, so it must be a local name.所以你说locals()不会显示x,因为它只是一个已定义的名称而没有分配给值? (或类似的东西)
@vikash因为变量在创建之前不存在。因为永远不会执行永远不会执行的赋值。
所以本地名称和局部变量是两个不同的东西。有没有办法看到本地名称列表,就像我们有局部变量的locals()。
@vikash为了更准确,Python实际上没有变量,它有可能绑定到一个或多个名称的对象。 locals()仅显示当前绑定到本地名称的对象。可能有一种简单的方法来显示所有被认为属于本地范围的名称,但我在手机上,所以我不容易调查。 ;)此外,评论不是讨论的地方。如果您无法在档案中找到相关内容,或者在Python聊天室中询问,也许可以提出一个新问题。
注意:优化器确实删除了无法访问的if False:块,至少在Python 3.5上(它不能在Py 2.x上,因为False不是那里的特殊常量,并且名称可能会反弹,即使这是一个可怕的想法)。但是x仍然是局部的,我假设是因为局部变量的扫描首先发生,并且不可达的块消除不会撤消x的局部性质。