首页 > 解决方案 > UnboundLocalError:局部变量赋值前引用

问题描述

我已经阅读了关于这个主题的类似问题,例如从这里这里这里,以及其他关于范围的问题,但我仍然不明白为什么答案不能解释这些测试用例。

首先看到这个:

def take_sum(a, b, c):
    return a+b+c

def main():
    print("take_sum" in globals())
    print(take_sum(1,2,3))

main()

回报:

True
6

那么这必须有效:

# TEST 1

def take_sum(a, b, c):
    return a+b+c

def main():
    if "take_sum" not in globals():
        take_sum = lambda a, b, c: a+b+c

    print(take_sum)

main()

错误的!

回报:UnboundLocalError: local variable 'take_sum' referenced before assignment

问题1take_sum如果它存在于全局范围内并且在main评估为False时如何在赋值之前引用它?

因此,让我们在 if 语句中添加一个else子句:

# TEST 2

def take_sum(a, b, c):
    return a+b+c

def main():
    if "take_sum" not in globals():
        take_sum = lambda a, b, c: a+b+c

    else:
        global take_sum

    print(take_sum)

main()

回报:SyntaxError: name 'take_sum' is assigned to before global declaration

问题 2:但是如果 TEST 1 中的错误说take_sum被引用但未分配,这怎么可能?

随后,切换 if 语句中的子句起作用:

# TEST 3

def take_sum(a, b, c):
    return a+b+c

def main():
    if "take_sum" in globals():
        global take_sum

    elif "take_sum" not in globals():
        take_sum = lambda a, b, c: a+b+c

    print(take_sum)
    print(take_sum(1,2,3))

main()

回报:

<function take_sum at 0x7fbf8b5bb160>
6

问题 3:为什么切换子句(与 TEST 2 相比)有效?

标签: python-3.xglobals

解决方案


函数中使用的名称是全局变量还是局部变量是在编译时确定的,而不是在运行时确定的。导致异常的函数正试图以两种方式使用它,要么访问全局变量,要么提供自己的局部变量替换,但 Python 的作用域规则不允许这样做。它需要是本地的或全局的,并且不能处于模糊的非此即彼的状态。

在您的测试 1 中,该函数引发异常,因为编译器看到代码可以分配take_sum为局部变量,因此它使take_sum代码中的所有引用都是局部的。take_sum一旦做出决定,您就不能再以正常方式查找全局变量。

语句实际上是一个global编译器指令,用于改变赋值使变量成为局部变量的假设。随后的分配将在全球范围内进行,而不是在本地进行。这不是在运行时执行的东西,这就是为什么您的其他两个测试用例让您如此困惑的原因。

测试 2 失败是因为在编译器已经看到一些代码对该名称进行了本地分配之后,它试图告诉编译器这take_sum是一个全局变量。在测试 3 中,语句首先出现,因此它使赋值(在另一个分支中!)赋值给一个全局变量。语句与赋值位于不同的分支实际上并不重要,编译器在编译时解释语句,而不是在运行时处理s 和s 的条件逻辑时。globalglobalglobalifelif

它可能有助于您了解如何反汇编main您使用dis.dis标准库中的函数编写的一些函数。您会看到有两组不同的字节码用于加载和存储变量,LOAD_GLOBAL/STORE_GLOBAL用于全局变量(在所有函数中用于获取类似printand的名称globals)和LOAD_FAST/STORE_FAST用于局部变量(例如a,b和)。我上面谈到的编译器行为归结为它为每次查找或分配选择的字节码。ctake_sum

如果我将mainTest 1 中的函数重命名为test1,这就是我反汇编它时得到的结果:

dis.dis(test1)
  2           0 LOAD_CONST               1 ('take_sum')
              2 LOAD_GLOBAL              0 (globals)
              4 CALL_FUNCTION            0
              6 CONTAINS_OP              1
              8 POP_JUMP_IF_FALSE       18

  3          10 LOAD_CONST               2 (<code object <lambda> at 0x0000019022A05F50, file "<ipython-input-23-0cc3c65f7038>", line 3>)
             12 LOAD_CONST               3 ('test1.<locals>.<lambda>')
             14 MAKE_FUNCTION            0
             16 STORE_FAST               0 (take_sum)

  5     >>   18 LOAD_GLOBAL              1 (print)
             20 LOAD_FAST                0 (take_sum)
             22 CALL_FUNCTION            1
             24 POP_TOP
             26 LOAD_CONST               0 (None)
             28 RETURN_VALUE

Disassembly of <code object <lambda> at 0x0000019022A05F50, file "<ipython-input-23-0cc3c65f7038>", line 3>:
  3           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 LOAD_FAST                2 (c)
              8 BINARY_ADD
             10 RETURN_VALUE

请注意,第take_sum5 行的查找位于字节码中的字节 20 上,它使用LOAD_FAST. 这是导致 的字节码UnboundLocalError,因为如果全局函数存在,则没有本地分配。

现在,让我们看一下测试 3:

dis.dis(test3)
  2           0 LOAD_CONST               1 ('take_sum')
              2 LOAD_GLOBAL              0 (globals)
              4 CALL_FUNCTION            0
              6 CONTAINS_OP              0
              8 POP_JUMP_IF_FALSE       12

  3          10 JUMP_FORWARD            18 (to 30)

  5     >>   12 LOAD_CONST               1 ('take_sum')
             14 LOAD_GLOBAL              0 (globals)
             16 CALL_FUNCTION            0
             18 CONTAINS_OP              1
             20 POP_JUMP_IF_FALSE       30

  6          22 LOAD_CONST               2 (<code object <lambda> at 0x0000019022A43500, file "<ipython-input-26-887b66de7e64>", line 6>)
             24 LOAD_CONST               3 ('test3.<locals>.<lambda>')
             26 MAKE_FUNCTION            0
             28 STORE_GLOBAL             1 (take_sum)

  8     >>   30 LOAD_GLOBAL              2 (print)
             32 LOAD_GLOBAL              1 (take_sum)
             34 CALL_FUNCTION            1
             36 POP_TOP

  9          38 LOAD_GLOBAL              2 (print)
             40 LOAD_GLOBAL              1 (take_sum)
             42 LOAD_CONST               4 (1)
             44 LOAD_CONST               5 (2)
             46 LOAD_CONST               6 (3)
             48 CALL_FUNCTION            3
             50 CALL_FUNCTION            1
             52 POP_TOP
             54 LOAD_CONST               0 (None)
             56 RETURN_VALUE

Disassembly of <code object <lambda> at 0x0000019022A43500, file "<ipython-input-26-887b66de7e64>", line 6>:
  6           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 LOAD_FAST                2 (c)
              8 BINARY_ADD
             10 RETURN_VALUE

这次查找take_sum发生在字节码 40 上,它是 a LOAD_GLOBAL(成功,因为有一个该名称的全局变量)。


推荐阅读