Python:为什么说只有引用的变量是隐式全局的?
在Python的常见问题解答中,我们可以看到:
在Python中,如果一个变量只在函数内部被引用,那么它会被默认为全局变量。
而在Python的函数定义教程中,我们又了解到:
执行一个函数时,会创建一个新的符号表,用于存放函数的局部变量。更具体地说,函数中的所有变量赋值都会把值存储在这个局部符号表里;而当引用变量时,Python会先在局部符号表中查找,如果找不到,就会去外层函数的局部符号表查找,再找全局符号表,最后查找内置名称的表。
我对教程中的内容理解得很清楚,但说“只在函数内部引用的变量被默认为全局变量”这句话对我来说有点模糊。
为什么说它们是默认为全局的呢?如果我们实际上是先查看局部符号表,然后再查找更“广泛”的符号表?这是不是意味着,如果你只在函数内部引用一个变量,就不需要担心它是局部的还是全局的?
4 个回答
跟其他一些编程语言不一样,Python 在查找变量名时,不会先在本地的符号表里找,如果找不到再去更大的范围里找。Python 会在编译时就确定一个变量是本地的,而不是在运行时。只要这个变量被赋值过(包括作为参数传入),它就被认为是本地变量。如果一个名字没有被赋值过,也没有明确声明为全局变量,那么它就被认为是全局变量,只会在全局命名空间里查找。这种做法让 Python 能够优化本地变量的访问(使用 LOAD_FAST
字节码),所以本地变量的访问速度更快。
关于闭包(在 Python 3 中还有 nonlocal
的情况)会有一些复杂的地方,但大体上就是这样。
Python中的名字解析规则有时被称为LEGB规则,这个名字是根据不同的作用域命名的。
当你在一个函数内部使用一个没有前缀的名字时,Python会依次在四个地方查找这个名字:首先是局部(L)作用域,然后是任何外部(E)函数和lambda表达式的局部作用域,接着是全局(G)作用域,最后是内置(B)作用域。Python会在找到名字的第一个地方停止查找。如果在这个过程中没有找到这个名字,Python就会报错。
- 名字的赋值默认会创建或改变局部名字。
- 名字的引用最多会在四个作用域中查找:局部作用域,然后是外部函数(如果有的话),接着是全局作用域,最后是内置作用域。
- 在global和nonlocal语句中声明的名字会分别映射到外部模块和函数的作用域。
换句话说,在一个函数def语句(或者一个lambda)内部赋值的所有名字默认都是局部的。函数可以自由使用在外部函数和全局作用域中赋值的名字,但如果想要改变这些名字,必须先声明它们为nonlocals和globals。
参考链接:http://goo.gl/woLW0F
示例
(下面有个总结)
这段话的意思是,如果一个变量在函数内部没有被赋值,那么它会被当作全局变量来处理。
这就解释了为什么下面的代码可以运行(a
被当作全局变量):
a = 1
def fn():
print a # This is "referencing a variable" == "reading its value"
# Prints: 1
但是,如果在函数内部给这个变量赋值,那么它会被当作局部变量来处理,这种情况在整个函数体内都是如此。
这包括在赋值之前的语句(见下面的例子)。
这就解释了为什么下面的代码不能运行。在这里,a
被当作局部变量:
a = 1
def fn():
print a
a = 2 # <<< We're adding this
fn()
# Throws: UnboundLocalError: local variable 'a' referenced before assignment
你可以通过在代码中使用 global a
这条语句来让 Python 把一个变量当作全局变量。如果这样做了,那么这个变量在整个函数体内都会被当作全局变量。
a = 1
def fn():
global a # <<< We're adding this
print a
a = 2
fn()
print a
# Prints: 1
# Then, prints: 2 (a changed in the global scope too)
总结
和你可能想的不同,Python不会在找不到局部变量 a
时自动回退到全局范围。
这意味着一个变量在整个函数体内要么是局部的,要么是全局的:它不能先是全局的然后再变成局部的。
至于一个变量是被当作局部还是全局,Python遵循以下规则:
- 如果只引用而没有赋值,则是全局变量
- 如果使用了
global
语句,则是全局变量 - 如果至少赋值一次(并且没有使用
global
),则是局部变量
进一步说明
实际上,“隐式全局”并不是真正的全局。可以这样理解:
- “局部”意味着“在函数内部的某个地方”
- “全局”意味着“在函数外部的某个地方”
所以,如果一个变量是“隐式全局”的(即“在函数外部”),那么会首先查找它的“外部作用域”:
a = 25
def enclosing():
a = 2
def enclosed():
print a
enclosed()
enclosing()
# Prints 2, as supplied in the enclosing scope, instead of 25 (found in the global scope)
现在,像往常一样,global
让你可以引用全局作用域。
a = 25
def enclosing():
a = 2
def enclosed():
global a # <<< We're adding this
print a
enclosed()
enclosing()
# Prints 25, as supplied in the global scope
如果你需要在 enclosed
中给 a
赋值,并且希望 a
的值在 enclosing
的作用域中改变,但不在全局作用域中改变,那么你需要使用 nonlocal
,这是 Python 3 中的新特性。在 Python 2 中,你不能这样做。