当我尝试这段代码:

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期望变量是本地的,但在每种情况下都有阻止赋值的逻辑错误的情况。


当前回答

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

其他回答

访问类变量的最佳方法是直接通过类名访问

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1

总结

Python decides the scope of the variable ahead of time. Unless explicitly overridden using the global or nonlocal (in 3.x) keywords, variables will be recognized as local based on the existence of any operation that would change the binding of a name. That includes ordinary assignments, augmented assignments like +=, various less obvious forms of assignment (the for construct, nested functions and classes, import statements...) as well as unbinding (using del). The actual execution of such code is irrelevant.

这在文档中也有解释。

讨论

与流行的观点相反,Python在任何意义上都不是一种“解释型”语言。(现在这种情况已经非常罕见了。)Python的参考实现以与Java或c#大致相同的方式编译Python代码:它被转换为虚拟机的操作码(“字节码”),然后模拟虚拟机。其他实现也必须编译代码;否则,eval和exec不能正确地返回一个对象,并且在不实际运行代码的情况下无法检测到SyntaxErrors。

Python如何确定变量作用域

在编译期间(无论是否在参考实现上),Python遵循简单的规则来决定函数中的变量作用域:

如果函数包含一个名称的全局或非局部声明,则该名称将分别被视为引用包含该名称的全局作用域或第一个封闭作用域。 否则,如果它包含任何用于更改名称的绑定(赋值或删除)的语法,即使代码在运行时不会实际更改绑定,该名称也是本地的。 否则,它引用包含该名称的第一个外围作用域,或者引用全局作用域。

Importantly, the scope is resolved at compile time. The generated bytecode will directly indicate where to look. In CPython 3.8 for example, there are separate opcodes LOAD_CONST (constants known at compile time), LOAD_FAST (locals), LOAD_DEREF (implement nonlocal lookup by looking in a closure, which is implemented as a tuple of "cell" objects), LOAD_CLOSURE (look for a local variable in the closure object that was created for a nested function), and LOAD_GLOBAL (look something up in either the global namespace or the builtin namespace).

这些名称没有“默认”值。如果在查找它们之前还没有分配它们,则会发生NameError。具体来说,对于本地查找,会发生UnboundLocalError;这是NameError的子类型。

特殊(和非特殊)情况

这里有一些重要的注意事项,请记住语法规则是在编译时实现的,没有静态分析:

It does not matter if the global variable is a builtin function etc., rather than an explicitly created global: def x(): int = int('1') # `int` is local! (Of course, it is a bad idea to shadow builtin names like this anyway, and global cannot help (just like using the same code outside of a function will still cause problems). See https://stackoverflow.com/questions/6039605.) It does not matter if the code could never be reached: y = 1 def x(): return y # local! if False: y = 0 It does not matter if the assignment would be optimized into an in-place modification (e.g. extending a list) - conceptually, the value is still assigned, and this is reflected in the bytecode in the reference implementation as a useless reassignment of the name to the same object: y = [] def x(): y += [1] # local, even though it would modify `y` in-place with `global` However, it does matter if we do an indexed/slice assignment instead. (This is transformed into a different opcode at compile time, which will in turn call __setitem__.) y = [0] def x(): print(y) # global now! No error occurs. y[0] = 1 There are other forms of assignment, e.g. for loops and imports: import sys y = 1 def x(): return y # local! for y in []: pass def z(): print(sys.path) # `sys` is local! import sys Another common way to cause problems with import is trying to reuse the module name as a local variable, like so: import random def x(): random = random.choice(['heads', 'tails']) Again, import is assignment, so there is a global variable random. But this global variable is not special; it can just as easily be shadowed by the local random. Deletion is also changing the name binding, e.g.: y = 1 def x(): return y # local! del y

有兴趣的读者,使用参考实现,鼓励使用dis标准库模块检查这些示例。

外围作用域和nonlocal关键字(在3.x中)

对全局关键字和非局部关键字进行必要的修改后,问题以同样的方式工作。(Python 2。X没有非局部的。)无论采用哪种方式,关键字都需要从外部作用域赋值给变量,但不需要仅仅查找它,也不需要更改所查找的对象。(同样:+=在列表上改变列表,但随后也将名称重新分配给相同的列表。)

关于全局变量和内置变量的特别说明

As seen above, Python does not treat any names as being "in builtin scope". Instead, the builtins are a fallback used by global-scope lookups. Assigning to these variables will only ever update the global scope, not the builtin scope. However, in the reference implementation, the builtin scope can be modified: it's represented by a variable in the global namespace named __builtins__, which holds a module object (the builtins are implemented in C, but made available as a standard library module called builtins, which is pre-imported and assigned to that global name). Curiously, unlike many other built-in objects, this module object can have its attributes modified and deld. (All of this is, to my understanding, supposed to be considered an unreliable implementation detail; but it has worked this way for quite some time now.)

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

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

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

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

例如:

def teams():
    ...

def some_other_method():
    teams = teams()

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

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

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

def teams():
    ...

def some_other_method():
    teams = get_teams()

这里有两个链接可能会有所帮助

1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has- a-value

2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-ref erence

link 1描述了错误UnboundLocalError。链接二可以帮助您重新编写测试函数。基于连杆二,原问题可以改写为:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)