当我尝试这段代码:

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 +=的访问而被发现,它突然变成了一个不同的变量。

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

其他回答

在下面n = num的情况下,n是一个局部变量,num是一个全局变量:

num = 10

def test():
  # ↓ Local variable
    n = num
       # ↑ Global variable
    print(n)
  
test()

因此,没有错误:

10

但是在下面的num = num的情况下,两边的num是局部变量,右边的num还没有定义:

num = 10

def test():
   # ↓ Local variable
    num = num
         # ↑ Local variable not defined yet
    print(num)
  
test()

所以,有下面的错误:

UnboundLocalError:赋值前引用的本地变量'num'

此外,即使删除num = 10,如下所示:

# num = 10 # Removed

def test():
   # ↓ Local variable
    num = num
         # ↑ Local variable not defined yet
    print(num)
  
test()

下面是同样的错误:

UnboundLocalError:赋值前引用的本地变量'num'

因此,为了解决上述错误,将global num放在num = num之前,如下所示:

num = 10

def test():
    global num # Here
    num = num 
    print(num)
  
test()

这样,上述误差就得到了解决,如下图所示:

10

或者,在num = num之前定义局部变量num = 5,如下所示:

num = 10

def test():
    num = 5 # Here
    num = num
    print(num)
  
test()

这样,上述误差就得到了解决,如下图所示:

5

总结

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

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

global c

作为函数的第一行。

至于python 3,现在有了

nonlocal c

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

C +=1赋值C, python假设赋值的变量是本地的,但在这种情况下,它没有在本地声明。

使用全局或非本地关键字。

Nonlocal只在python 3中有效,所以如果你在使用python 2并且不想让你的变量为全局变量,你可以使用一个可变对象:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()

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

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

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