Python变量的作用域和命名空间

目录
  1. 作用域和命名空间
  2. global和nonlocal语句

作者:杨冬 欢迎转载,也请保留这段声明。谢谢!
出处:https://andyyoung01.github.io/http://andyyoung01.16mb.com/

Python中的命名空间(namespace)是标识符到对象的映射,通常以字典的形式表现出来。变量的作用域(scope)是Python程序的文本区域,在该区域某个命名空间中的名字可以被直接引用。赋值(assignment)操作不会拷贝,只是把标识符和对象做一个绑定。

作用域和命名空间

当一段代码在Python中执行时,它有三个命名空间:localglobalbuilt-in。在执行过程中遇到了某个标识符时,Python首先尝试在local命名空间中查找它,如果没有找到,再在global命名空间中查找,如果还是没有找到,接着在built-in命名空间中查找。如果都不存在,则被认为是一个错误,会抛出一个“NameError”异常。如图所示:
{% asset_img 1.png “标识符在命名空间中的查找顺序”%}

命名空间都是有创建时间和生存期的。对于Python built-in names组成的命名空间,它在Python解释器启动的时候被创建,在解释器退出的时候才被删除;对于一个Python模块的global namespace,它在这个module被import的时候创建,在解释器退出的时候退出;对于一个函数的local namespace,它在函数每次被调用的时候创建,函数返回的时候被删除。下面看一个例子:

>>> def f(x):
    print ("global: ",globals())
    print ("Entry local: ",locals())
    y = x
    print ("Exit local: ",locals())

>>> z = 2
>>> globals()
{'__package__': None, 'f': <function f at 0x0000000000B76EA0>, '__doc__': None, '__name__': '__main__', 'z': 2, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__builtins__': <module 'builtins' (built-in)>}
>>> f(z)
global:  {'__package__': None, 'f': <function f at 0x0000000000B76EA0>, '__doc__': None, '__name__': '__main__', 'z': 2, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__builtins__': <module 'builtins' (built-in)>}
Entry local:  {'x': 2}
Exit local:  {'y': 2, 'x': 2}
>>> 

可以看到,刚进入函数f(x)时,只有参数x在f的local命名空间,之后y加入了f的local命名空间。f的global命名空间与我们交互会话的global命名空间相同,因为f是在交互会话时定义的。
再来看一个引入模块的例子,假如有如下的文件:

scopetest.py
1
2
3
4
5
6
7
8
9
"""scopetest: our scope test module"""
v = 6
def f(x):
"""f: scope test function"""
print("global: ", list(globals().keys()))
print("entry local:", locals())
y = x
w = v
print("exit local:", locals())

为了使结果更清晰,下面使用了keys()方法只显示出globals方法返回字典的keys(标识符):

>>> list(globals().keys())
['__name__', '__loader__', '__doc__', '__spec__', '__package__', '__builtins__']
>>> import scopetest
>>> z = 2
>>> list(globals().keys())
['__name__', '__loader__', '__doc__', 'z', '__spec__', '__package__', 'scopetest', '__builtins__']
>>> scopetest.f(z)
global:  ['__name__', '__loader__', '__doc__', '__file__', '__spec__', '__package__', 'f', '__builtins__', '__cached__', 'v']
entry local: {'x': 2}
exit local: {'y': 2, 'w': 6, 'x': 2}
>>> 

可见,在交互式会话中,随着模块的import和变量z的定义,交互会话的global命名空间增加了scopetest和z标识符。在scopetest模块的f函数的global命名空间中,包含了方法f和v(但是并没有交互会话中的z)。

[总结]一个模块的引入,函数的调用,类的定义都会引入命名空间;函数中的再定义函数,类中的成员函数定义会在局部namespace中再次引入局部namespace。另外,赋值语句是起一个绑定或重绑定的作用(bind or rebind)。函数调用的参数传递是赋值,不是拷贝。

global和nonlocal语句

global语句用来声明一系列变量,这些变量会引用到当前模块的全局命名空间的变量(module-level namespace),如果该变量没有定义,也会在全局空间中添加这个变量。

global var1, var2

nonlocal语句(nonlocal是Python3.2引入的)

Python2.7中还没有nonlocal语句。nonlocal语句用来声明一系列的变量,这个声明会从声明处从里到外的namespace去搜寻这个变量(the nearest enclosing scope),直到模块的全局域(不包括全局域),找到了则引用这个命名空间的这个名字和对象,若作赋值操作,则直接改变外层域中的这个名字的绑定。nonlocal语句声明的变量不会 在当前scope的namespace字典中加入一个key-value对,如果在外层域中没有找到,则如下报错。

>>> SyntaxError: no binding for nonlocal 'spam' found

一个nonlocal和global的测试例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("after local assignment:", spam) #输出:test spam
do_nonlocal()
print("after nonlocal asssignment:", spam) #输出:nonlocal spam
do_global()
print("after global assignment:", spam) #输出:nonlocal spam
test()
print("in global scope:", spam) #输出:global spam

第5行的语句:nonlocal spam 没有在函数do_nonlocal()的域中创建一个变量,而是去引用到了外层的,10行定义的spam。
第8行的global spam,在全局域中创建了一个name,9行将其绑定在字符串常量对象”global spam”上。

本文参考了http://www.cnblogs.com/livingintruth/p/3296010.html 这篇文章及“The Quick Python Book Second Edition”。