UnboundLocalError: local variable <function> referenced before assignment

UnboundLocalError: local variable <function> referenced before assignment

我已经阅读了关于此主题的类似问题,例如来自 here, , and here,以及范围内的其他问题,但我仍然不明白为什么答案没有解释这些测试用例。

首先看到这个:

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

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

main()

Returns:

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()

错了!

Returns: UnboundLocalError: local variable 'take_sum' referenced before assignment

问题 1:如果 take_sum 存在于全局范围内并且 main 中的 if 语句求值为 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()

Returns: 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()

Returns:

<function take_sum at 0x7fbf8b5bb160>
6

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

举个例子

您正在将变量引用预定义为全局变量

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

输出:<function main.<locals>.<lambda> at 0x7f08c2c54820>

按预期删除 not'take_sum' is not defined


现在,创建一个全局的

def take_sum(*args):
    return sum(args)

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

这次输出的是外层

删除not,然后删除本地


因此,如果您尝试在 else 语句中创建 global take_sum,那么正如错误所说,assigned to before global declaration,并且 global 似乎在不考虑条件的情况下被评估

函数中使用的名称是全局变量还是局部变量是在编译时确定的,而不是在运行时确定的。导致异常的函数试图同时使用这两种方法,要么访问全局变量,要么提供自己的局部变量替换,但 Python 的范围规则不允许这样做。它需要是局部的或全局的,不能处于模糊的either/or状态。

在您的测试 1 中,该函数引发异常,因为编译器发现代码 可以 分配给 take_sum 作为局部变量,因此它使所有代码中对 take_sum 的引用是本地的。一旦做出决定,您将无法再以正常方式查找全局变量 take_sum

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

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

它可能有助于您理解如何反汇编您使用标准库中的 dis.dis 函数编写的某些 main 函数。你会看到有两组不同的字节码用于变量的加载和存储,LOAD_GLOBAL/STORE_GLOBAL 用于全局变量(在你的所有函数中用于获取名称,如 printglobals) 和 LOAD_FAST/STORE_FAST 用于局部变量(如 abc in take_sum ).我在上面谈到的编译器行为归结为它为每次查找或赋值选择的字节码。

如果我将测试 1 中的 main 函数重命名为 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

请注意,第 5 行的 take_sum 查找位于字节码中的字节 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 上,它是一个 LOAD_GLOBAL(由于存在同名的全局变量,因此成功)。