作用域规则的简要描述
Python 的作用域规则到底是什么呢?
假设我有一段代码:
code1
class Foo:
code2
def spam.....
code3
for code4..:
code5
x()
那么 x
这个变量会在哪里被找到呢?以下是一些可能的地方:
- 在外部的源文件中
- 在类的命名空间里
- 在函数的定义中
- 在 for 循环的索引变量里
- 在 for 循环内部
此外,还有执行时的上下文,比如当函数 spam
被传递到其他地方时。还有可能 lambda 函数 的传递方式会有所不同?
一定有一个简单的参考或算法可以帮助理解。对于中级的 Python 程序员来说,这个问题确实让人困惑。
9 个回答
关于Python3的时间问题,之前没有详细的答案,所以我在这里做了一个解答。这里大部分内容在Python 3文档的4.2.2 名称解析中都有详细说明。
根据其他答案的介绍,Python有四个基本的作用域,简称LEGB,分别是局部(Local)、封闭(Enclosing)、全局(Global)和内置(Builtin)。除了这些,还有一个特殊的作用域,叫类体,它不算是方法定义时的封闭作用域;在类体内的任何赋值操作都会让变量从此开始绑定在类体内。
特别要注意的是,除了def
和class
,其他的代码块都不会创建变量作用域。在Python 2中,列表推导式不会创建变量作用域,但在Python 3中,列表推导式中的循环变量会在一个新的作用域中创建。
为了演示类体的特殊性
x = 0
class X(object):
y = x
x = x + 1 # x is now a variable
z = x
def method(self):
print(self.x) # -> 1
print(x) # -> 0, the global x
print(y) # -> NameError: global name 'y' is not defined
inst = X()
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0)
因此,与函数体不同的是,你可以在类体内重新赋值给同一个变量名,从而得到一个同名的类变量;之后对这个名字的查找会指向类变量。
许多Python新手会感到惊讶的是,for
循环并不会创建一个变量作用域。在Python 2中,列表推导式也不会创建作用域(而生成器和字典推导式会!)相反,它们会泄露值到函数或全局作用域中:
>>> [ i for i in range(5) ]
>>> i
4
在Python 2中,推导式可以作为一种巧妙(或者说糟糕)的方式,在lambda表达式中创建可修改的变量——lambda表达式会创建一个变量作用域,就像def
语句一样,但在lambda中不允许有语句。因为在Python中,赋值是一个语句,所以在lambda中不允许赋值,但列表推导式是一个表达式……
这种行为在Python 3中得到了修复——没有推导式或生成器会泄露变量。
全局作用域实际上指的是模块作用域;主Python模块是__main__
;所有导入的模块都可以通过sys.modules
变量访问;要访问__main__
,可以使用sys.modules['__main__']
,或者import __main__
;在这里访问和赋值属性是完全可以的;它们会在主模块的全局作用域中显示为变量。
如果在当前作用域(类作用域除外)对某个名字进行了赋值,它就会被认为属于这个作用域;否则,它会被认为属于任何封闭作用域中对该变量进行赋值的作用域(可能还没有被赋值,或者根本没有赋值),最后是全局作用域。如果变量被认为是局部的,但还没有设置,或者已经被删除,读取这个变量的值会导致UnboundLocalError
,这是NameError
的一个子类。
x = 5
def foobar():
print(x) # causes UnboundLocalError!
x += 1 # because assignment here makes x a local variable within the function
# call the function
foobar()
作用域可以声明它明确想要修改全局(模块作用域)变量,使用global关键字:
x = 5
def foobar():
global x
print(x)
x += 1
foobar() # -> 5
print(x) # -> 6
即使在封闭作用域中被遮蔽,这也是可能的:
x = 5
y = 13
def make_closure():
x = 42
y = 911
def func():
global x # sees the global value
print(x, y)
x += 1
return func
func = make_closure()
func() # -> 5 911
print(x, y) # -> 6 13
在Python 2中,没有简单的方法可以修改封闭作用域中的值;通常通过使用一个可变值来模拟,比如一个长度为1的列表:
def make_closure():
value = [0]
def get_next_value():
value[0] += 1
return value[0]
return get_next_value
get_next = make_closure()
print(get_next()) # -> 1
print(get_next()) # -> 2
但在Python 3中,nonlocal
来拯救我们:
def make_closure():
value = 0
def get_next_value():
nonlocal value
value += 1
return value
return get_next_value
get_next = make_closure() # identical behavior to the previous example.
nonlocal
文档中提到:
在nonlocal语句中列出的名字,与在global语句中列出的名字不同,必须指向封闭作用域中已存在的绑定(无法明确确定新绑定应该创建在哪个作用域)。
也就是说,nonlocal
总是指向最内层的非全局封闭作用域,其中名字已经被绑定(即被赋值,包括用作for
目标变量、在with
语句中,或作为函数参数)。
任何不被认为是当前作用域或任何封闭作用域的局部变量,都是全局变量。全局名字在模块全局字典中查找;如果找不到,就会从内置模块中查找;模块的名字在Python 2到Python 3之间发生了变化;在Python 2中是__builtin__
,而在Python 3中叫builtins
。如果你对内置模块的属性进行赋值,它将会在之后对任何模块作为可读的全局变量可见,除非那个模块用同名的全局变量遮蔽了它。
读取内置模块也很有用;假设你想在文件的某些部分使用Python 3风格的print函数,但文件的其他部分仍然使用print
语句。在Python 2.6-2.7中,你可以通过以下方式获取Python 3的print
函数:
import __builtin__
print3 = __builtin__.__dict__['print']
from __future__ import print_function
实际上并没有在Python 2中导入print
函数——它只是禁用了当前模块中print
语句的解析规则,把print
当作其他变量标识符来处理,从而允许在内置模块中查找print
函数。
简单来说,在Python中,只有函数定义会引入一个新的作用域。类有点特别,类里面直接定义的东西会放在类的命名空间里,但在类的方法(或者嵌套类)中是不能直接访问这些东西的。
在你的例子中,x会在以下三个作用域中被查找:
spam的作用域 - 包含了code3和code5中定义的所有内容(还有code4,你的循环变量)
全局作用域 - 包含了code1中定义的所有内容,还有Foo(以及它后面可能的变化)
内置命名空间。这是个特殊情况 - 这里包含了各种Python内置的函数和类型,比如len()和str()。一般来说,用户的代码不应该修改这里的内容,所以你可以期待它只包含标准函数,没别的。
当你引入一个嵌套函数(或者lambda)时,会出现更多的作用域。不过这些作用域的行为基本上是你能预料到的。嵌套函数可以访问本地作用域中的所有内容,以及外层函数的作用域中的内容。例如:
def foo():
x=4
def bar():
print x # Accesses x from foo's scope
bar() # Prints 4
x=5
bar() # Prints 5
限制:
虽然可以访问本地函数以外的作用域中的变量,但如果没有额外的语法,不能把它们重新绑定到新的参数上。相反,赋值会创建一个新的局部变量,而不会影响父作用域中的变量。例如:
global_var1 = []
global_var2 = 1
def func():
# This is OK: It's just accessing, not rebinding
global_var1.append(4)
# This won't affect global_var2. Instead it creates a new variable
global_var2 = 2
local1 = 4
def embedded_func():
# Again, this doen't affect func's local1 variable. It creates a
# new local variable also called local1 instead.
local1 = 5
print local1
embedded_func() # Prints 5
print local1 # Prints 4
如果你想在函数作用域内真正修改全局变量的绑定,需要用global关键字来指定这个变量是全局的。例如:
global_var = 4
def change_global():
global global_var
global_var = global_var + 1
目前还没有办法对外层函数作用域中的变量做同样的事情,但Python 3引入了一个新的关键字"nonlocal
",它的作用类似于global,但适用于嵌套函数的作用域。
其实,关于Python中作用域解析的一个简单规则,来自于《学习Python》第三版。(这些规则是针对变量名的,不是属性。如果你没有用点号引用它,这些规则就适用。)
LEGB规则
Local(局部)— 在函数(
def
或lambda
)内部以任何方式赋值的名字,并且在该函数中没有声明为全局的Enclosing-function(封闭函数)— 在任何静态封闭函数的局部作用域中赋值的名字(
def
或lambda
),从内到外Global(全局)— 在模块文件的顶层赋值的名字,或者在文件中的
def
里执行global
语句所声明的名字Built-in(内置)— 在内置名字模块中预先定义的名字:
open
、range
、SyntaxError
等
所以,在以下情况下
code1
class Foo:
code2
def spam():
code3
for code4:
code5
x()
这个for
循环没有自己的命名空间。按照LEGB的顺序,作用域会是:
- L: 在
def spam
中的局部(在code3
、code4
和code5
中) - E: 任何封闭的函数(如果整个例子在另一个
def
中) - G: 模块中是否有声明为全局的
x
(在code1
中)? - B: Python中的任何内置
x
。