【问题标题】:Python local variable compile principlePython局部变量编译原理
【发布时间】:2017-02-25 04:05:29
【问题描述】:
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解释器编译的机制原理吗?

【问题讨论】:

  • 这可能是相关的:Short Description of Scoping Rules
  • 如果以下答案之一解决了您的问题,您应该接受它(单击相应答案旁边的复选标记)。这有两件事。它让每个人都知道您的问题已得到您满意的解决,并为帮助您的人提供帮助。有关完整说明,请参阅here

标签: python compilation interpreter


【解决方案1】:

因此,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)或同一函数定义中的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,这将导致 xlocal 变量访问,并且仅局部变量。由于尚未设置 x,因此会抛出 UnboundLocalError。另一方面,名称 print 从未分配给,因此在此函数中被视为全局名称,因此其值加载为 LOAD_GLOBAL

【讨论】:

    【解决方案2】:

    函数中使用的名称对于整个函数体只能有一个作用域。范围是在编译时确定的(而不是在函数运行时)。

    如果函数中的任何地方都有一个名称的赋值(不管它是否会在函数被调用时运行),编译器默认将该名称视为函数的本地名称。您可以使用globalnonlocal 语句明确告诉它使用不同的范围。

    一种特殊情况是在一个函数的主体中分配了一个名称,并从第一个函数中定义的另一个函数访问该名称。这样的变量将被放入一个特殊的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 调用在它被分配到本地命名空间之前尝试使用它。

    【讨论】:

    • 感谢您提及关闭。
    【解决方案3】:

    x = 3 不可访问这一事实无关紧要。该函数分配给它,因此它必须是本地名称。

    请记住,整个文件是在执行开始之前编译的,但函数是在执行阶段定义的,当编译的函数定义块执行时,会创建函数对象。

    复杂的优化器可以消除无法访问的代码,但 CPython 的优化器并不那么聪明——它只执行非常简单的锁孔优化。

    要更深入地了解 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() 用于局部变量。
    • 注意:优化器实际上确实删除了无法访问的 if False: 块,至少在 Python 3.5 上(它不能在 Py 2.x 上,因为 False 不是那里的特殊常量,并且名称可能会反弹,即使这是一个糟糕的主意)。但是x 仍然是本地的,我假设是因为首先发生了对局部变量的扫描,并且无法访问的块消除不会撤消x 的本地性质。
    猜你喜欢
    • 1970-01-01
    • 2019-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多