Python局部变量编译原理


问题内容
def fun():  
    if False:
        x=3
    print(locals())
    print(x)
fun()

输出和错误消息:

{}
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-57-d9deb3063ae1> in <module>()
      4     print(locals())
      5     print(x)
----> 6 fun()

<ipython-input-57-d9deb3063ae1> 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解释器的编译机制原理吗?


问题答案:

因此,Python始终会将每个函数中的每个名称归类为 localnon-localglobal之一
。这些名称范围是排他的;在每个函数中(嵌套函数中的名称都有其自己的命名范围),每个名称只能属于这些类别之一。

当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带有的节点,则ctx=Store()该名称被认为是封闭的
本地 名称FunctionDef,除非在同一函数定义中用global(即global x)或nonlocalnonlocal 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