总结
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.)