【问题标题】:UnboundLocalError: local variable <function> referenced before assignmentUnboundLocalError:分配前引用的局部变量 <function>
【发布时间】:2021-10-16 04:50:08
【问题描述】:

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

首先看到这个:

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

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

返回: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.x globals


    【解决方案1】:

    举个例子

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

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

    输出:&lt;function main.&lt;locals&gt;.&lt;lambda&gt; at 0x7f08c2c54820&gt;

    按预期删除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 在不考虑条件的情况下被评估

    【讨论】:

    • 那是我没有遵循的地方:TEST 1 中的 if-condition 计算结果为 False,所以应该没有赋值,当光标返回到 print 语句时,它应该打印的位置take_sum 在内存中就像在测试 3 中一样......我错过了什么
    • 请注意,您在使用if False 时会遇到同样的错误。我怀疑您正在尝试做一些与用本地函数覆盖全局函数非常不同的事情?
    【解决方案2】:

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

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

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

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

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

    如果我将测试 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(因为有一个同名的全局变量,所以它成功了)。

    【讨论】:

      猜你喜欢
      • 2017-08-10
      • 2020-01-16
      • 2019-12-05
      • 2017-09-19
      • 2020-09-26
      • 2022-01-02
      • 2019-03-22
      • 2019-01-24
      • 2021-07-01
      相关资源
      最近更新 更多