当我尝试这段代码:

a, b, c = (1, 2, 3)

def test():
    print(a)
    print(b)
    print(c)
    c += 1
test()

我从打印(c)行得到一个错误,它说:

UnboundLocalError: local variable 'c' referenced before assignment

在Python的新版本中,或者

UnboundLocalError: 'c' not assigned

在一些老版本中。

如果注释掉c += 1,两次打印都成功。

我不明白:如果c不行,为什么打印a和b可以?c += 1是如何导致print(c)失败的,即使它出现在代码的后面?

赋值c += 1似乎创建了一个局部变量c,它优先于全局变量c。但是一个变量如何在它存在之前“窃取”作用域呢?为什么c是局部的?


请参见在函数中使用全局变量,了解如何从函数中重新分配全局变量的问题,以及是否可以在python中修改位于外部(封闭)但不是全局范围的变量?用于从封闭函数(闭包)重新赋值。

参见为什么不需要'global'关键字来访问全局变量?对于OP预期错误但没有得到错误的情况,从简单地访问一个没有global关键字的全局变量。

参见如何在Python中“解除绑定”名称?什么代码可以导致“UnboundLocalError”?对于OP期望变量是本地的,但在每种情况下都有阻止赋值的逻辑错误的情况。


当前回答

Python解释器将把函数作为一个完整的单元来读取。我认为它是分两次读取,一次是收集它的闭包(局部变量),然后再次将其转换为字节码。

我相信你已经意识到,在'='左边使用的任何名称都是隐式的局部变量。我不止一次因为改变一个变量对a +=的访问而被发现,它突然变成了一个不同的变量。

我还想指出的是,它与全局作用域并没有什么特别的关系。嵌套函数也有同样的行为。

其他回答

看一看这个分解程序可能会清楚发生了什么:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

As you can see, the bytecode for accessing a is LOAD_FAST, and for b, LOAD_GLOBAL. This is because the compiler has identified that a is assigned to within the function, and classified it as a local variable. The access mechanism for locals is fundamentally different for globals - they are statically assigned an offset in the frame's variables table, meaning lookup is a quick index, rather than the more expensive dict lookup as for globals. Because of this, Python is reading the print a line as "get the value of local variable 'a' held in slot 0, and print it", and when it detects that this variable is still uninitialised, raises an exception.

如果定义了与方法同名的变量,也可以得到此消息。

例如:

def teams():
    ...

def some_other_method():
    teams = teams()

解决方案是将方法teams()重命名为get_teams()。

因为它只在本地使用,所以Python消息相当具有误导性!

你最终会得到这样的结果:

def teams():
    ...

def some_other_method():
    teams = get_teams()

Python对函数中的变量的处理方式不同,这取决于你是在函数内部还是函数外部为变量赋值。如果变量是在函数中赋值的,默认情况下它被视为局部变量。因此,当您取消注释该行时,您将尝试在为局部变量c赋值之前引用它。

如果你想让变量c引用在函数之前赋值的全局c = 3, put

global c

作为函数的第一行。

至于python 3,现在有了

nonlocal c

您可以使用它来引用最近的包含c变量的外围函数作用域。

在初始化之后,通常在循环或条件块中对变量使用del关键字时,也会发生此问题。

当您尝试传统的全局变量语义时,Python有相当有趣的行为。我不记得细节了,但你可以读取在'global'范围内声明的变量的值,但如果你想修改它,你必须使用global关键字。试着把test()改成这样:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

同样,你得到这个错误的原因是因为你也可以在函数中声明一个与“全局”同名的新变量,并且它将完全独立。解释器认为你试图在这个作用域中创建一个名为c的新变量,并在一个操作中修改它,这在Python中是不允许的,因为这个新的c没有初始化。