Python Scopes and Namespaces

Python的作用域和命名空间

Namespaces

命名空间是命名到对象的映射.目前大多数命名空间是用Python的字典(dict)实现的,但是具体如何实现无关紧要,除非出于性能的考虑,并且将来可能改变其实现方式.例如命名空间是:built-in(builtins模块)的命名的集合,包括例如abs()函数或者built-in异常命名;函数调用的本地命名.在某种意义上对象的属性集也是一个命名空间.关于命名空间需要明白一件很重要的事就是不同命名空间间绝对没有任何关系;例如,两个模块可能都定义了一个函数maximize而不会混淆--用户必须以模块名为前缀来引用它们.

命名空间在不同的时间被创建,并有不同生命周期.包含built-in命名的命名空间会在GIL启动的时候创建,而且永远不会被删除.对于模块的全局命名空间在模块的定义被读入时创建;通常,模块命名空间也生存到GIL退出前.无论从脚本文件或是交互的语句,在GIL的顶层调用执行的语句被认为是__main__模块的一部分,所以它们有它们自己的全局命名空间.
*某些程序中:if _name_==_main_ 用来判断脚本文件是否是被GIL顶层调用,如果不是可以屏蔽一些不必要的操作,例如不必要的提示输出.*

当函数被调用时它的本地or局部(local)命名空间被创建,当该函数返回(return)或抛出未被函数中处理的异常时命名空间被删除.实际上用遗忘来描述发生了什么更为贴切.如果重新调用改函数,命名空间会重新创建.

Scopes

作用域是python程序可以直接访问命名空间的文本区域.这里直接访问是指一个未绑定命名的引用在命名空间试图找到这个命名.

尽管作用域是静态决定的,但都是动态使用的.在任何执行时刻,至少有三种嵌套作用域,其命名空间可以直接访问的:

  • 最先搜索的是包含本地命名的最内部作用域
  • 从最近的封闭作用域开始搜索任何封闭函数,包含non-local也包含non-global命名(闭包不熟悉,)
  • 包含当前模块的全局命名
  • 最外层的作用域(最后搜索)是包含built-in命名的命名空间

例子:

def scope_test():
    def do_local():
        spam = "local spam"
        print('dict in do_local()',locals(),'and id',id(spam))

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"
        print('dict in do_nonlocal()',locals(),'and id',id(spam))

    def do_global():
        global spam
        spam = "global spam"
        print('dict in do_global()',locals(),'and id',id(spam))

    spam = "test spam"

    do_local()
    print("After local assignment:", spam,'and id',id(spam))

    do_nonlocal()
    print("After nonlocal assignment:", spam,'and id',id(spam))

    do_global()
    print("After global assignment:", spam,'and id',id(spam))

    print('dict in scope_test',locals(),'and id',id(spam))

scope_test()
print("In global scope:", spam,'and global dict',globals(),'and id',id(spam))

output:

dict in do_local() {'spam': 'local spam'} and id 140120830499184
After local assignment: test spam and id 140120830499440
dict in do_nonlocal() {'spam': 'nonlocal spam'} and id 140120830499248
After nonlocal assignment: nonlocal spam and id 140120830499248
dict in do_global() {} and id 140120830499376
After global assignment: nonlocal spam and id 140120830499248
dict in scope_test {'do_global': <function scope_test.<locals>.do_global at 0x7f706c52e1e0>, 'do_nonlocal': <function scope_test.<locals>.do_nonlocal at 0x7f706c52e158>, 'do_local': <function scope_test.<locals>.do_local at 0x7f706c52e0d0>, 'spam': 'nonlocal spam'} and id 140120830499248
In global scope: global spam and global dict {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f706c5e5080>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/katachi/PycharmProjects/OtherPrj/pythontest/namespace.py', '__cached__': None, 'scope_test': <function scope_test at 0x7f706c60be18>, 'spam': 'global spam'} and id 140120830499376

  • 关于nonlocal:
    nonlocal语句用来声明一系列的变量,这个声明会从声明处从里到外的namespace去搜寻这个变量(the nearest enclosing scope),直到模块的全局域(不包括全局域),找到了则引用这个命名空间的这个名字和对象,若作赋值操作,则直接改变外层域中的这个名字的绑定。nonlocal列出的命名必须引用封闭范围中的预先存在的绑定
  • 关于locals()和globals()
    分别可以访问本地Namespace和全局Namespace

根据id和locals()的结果,do_local()中的spam属于do_local()的本地命名空间,不能修改scope_test()中的spam.
do_nonlocal()中用关键字nonlocal声明spam,该spam不是新建的,它会向外层寻找,最终绑定scope_test()中的spam.
do_global()中使用关键字global声明spam,但是全局Namespace预先没有spam,则会新建一个spam,这一点通过id()结果也看得出来.

简单说,命名空间就是确定某个命名对应的对象,而作用域则是确定改命名到底是哪个命名空间的.
python确定某个命名时,有从"内"往"外"的特点,global使得命名可以绑定全局命名空间的对象,而nonlocal则从上一层往外寻找,直到找到或者抛出异常.


REF:
https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces
https://docs.python.org/3/reference/simple_stmts.html#nonlocal
http://www.cnblogs.com/livingintruth/p/3296010.html

posted @ 2018-11-21 22:32  katachi  阅读(261)  评论(0编辑  收藏  举报