转载

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

作者:杨冬 欢迎转载,也请保留这段声明。谢谢!

出处: https://andyyoung01.github.io/ 或 http://andyyoung01.16mb.com/

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

作用域和命名空间

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

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

命名空间都是有创建时间和生存期的。对于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
"""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的测试例子:

deftest():
    defdo_local():
        spam = "local spam"
    defdo_nonlocal():
        nonlocal spam    
        spam = "nonlocal spam"
    defdo_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”。

原文  https://andyyoung01.github.io/2017/02/18/Python变量的作用域和命名空间/
正文到此结束
Loading...