操作变量时出现 UnboundLocalError 产生不一致的行为

UnboundLocalError when manipulating variables yields inconsistent behavior

在Python中,以下代码有效:

a = 1
b = 2

def test():
    print a, b

test()

以下代码有效:

a = 1
b = 2

def test():
    if a == 1:
        b = 3
    print a, b

test()

但以下有效:

a = 1
b = 2

def test():
    if a == 1:
        a = 3
    print a, b

test()

最后一个块的结果是 UnboundLocalError 消息,说 a 在赋值之前被引用。

我知道如果我在 test() 定义中添加 global a 可以使最后一个块工作,所以它知道我在说哪个 a

为什么我在给 b 赋新值时没有收到错误消息?

我是否创建了一个局部 b 变量,它不会对我大喊大叫,因为我没有在赋值之前尝试引用它?

但如果是这样的话,为什么我可以在第一个块的情况下 print a, b,而不必事先声明 global a, b

当你修改a时,它变成一个局部变量。当您只是引用它时,它是一个全局的。你还没有在本地范围内定义a,所以你不能修改它。

如果你想修改一个全局的,你需要在你的本地范围内调用它。

看看下面的字节码

import dis

a = 9 # Global

def foo():
    print a # Still global

def bar():
    a += 1 # This "a" is local


dis.dis(foo)

输出:

  2           0 LOAD_GLOBAL              0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE

对于第二个函数:

dis.dis(bar)

输出:

  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD
              7 STORE_FAST               0 (a)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

第一个函数的字节码加载全局 a (LOAD_GLOBAL) 因为它只是被引用。第二个函数的字节码 (LOAD_FAST) 尝试 加载本地 a 但尚未定义。

第二个函数起作用的唯一原因是 a 等于 1。如果 a 不是 1,则不会发生对 b 的本地分配,并且您会收到相同的错误。

让我给你 link 到 docs 明确提到的地方。

If a variable is assigned a new value anywhere within the function’s body, it’s assumed to be a local.

(强调我的)

因此你的变量a是一个局部变量而不是全局变量。这是因为你有一个赋值语句,

a = 3

在你代码的第三行。这使得 a 成为局部变量。并且在声明之前引用局部变量会导致错误,即 UnboundLocalError.

但是在您的第二个代码块中,您没有进行任何此类赋值语句,因此您不会收到任何此类错误。

另一个有用的 link 是 this

Raised when a reference is made to a local variable in a function or method, but no value has been bound to that variable.

因此您指的是您在下一行中创建的局部变量。

要防止这种情况有两种方法

  • 好方法 - 传递参数

    将函数定义为 def test(a): 并将其调用为 test(a)

  • 错误的方式 - 使用 global

    在函数调用的顶部有一行 global a

Python 范围规则有点棘手!你需要掌握它们才能掌握这门语言。查看 this

在第三个块中,编译器已将 a 标记为局部变量,因为它正在被赋值,因此当它在表达式中使用时,它会在局部范围内查找。由于那里不存在,因此引发异常。

在第二个块中,编译器将 b 标记为局部变量而不是 a,因此在访问 a 时没有异常,因为将搜索外部范围。