有人把 Python 作用域解析规则总结为 LEGB , 确实够精炼简要。但我还要继续补遗:
其一,所谓 global 命名空间,都是隶属某模块名的。于是当解释执行(注意没有加 -m
参数)某 Python 源代码文件,或在交互式环境里,这个文件或交互式环境本身所谓的 global 命名空间,其实同样隶属一个模块,它的名字就叫 __main__ . 如果您在被解释执行的文件或交互式环境定义一个函数对象 a
, 那么 print(a.__module__)
会返回一个 str 对象,其值为 __main__
. 说来好笑,我曾经纳闷 有什么办法能访问到这模块 ,后来想想,其实我本来不就在文件和交互式环境本身里面啊,当然能随便直接访问了。
其二, import a
, print(a.b)
很好理解,会先在 __main__
模块解析到 a
这模块的名字,接着就在后者的 global 命名空间继续解析到 b
.
其三, print(sum.__module__)
, def sum(): pass
, print(sum.__module__)
会先后输出 __builtins__
和 __main__
, 但这不是因为 rebinding 了 builtins 命名空间的 sum
,而是在 __main__
模块的 global 命名空间定义了 sum
对象且后者被先解析到而已。此外您也可以 把 __builtins__
当成模块对象(注意不是 str 对象)来访问它 global 命名空间的值 ,比如 print(__builtins__.__dict__)
就返回一个字典对象,后者把 str 对象映射为您可以访问的内置对象。说起来 其实这里很微妙 ,因为 __builtins__
本身应该被解析为 __main__
模块 global 命名空间的某对象了,但您并没有 import 它, 也就是说它从一开始就默认存在,但偏偏又不隶属「如同字面意义上所真正内置」的 __builtins__
模块的 global 命名空间! 事实上您还可以显式 import builtins
, 再继续访问它的属性……比如 print(builtins.__dict__)
就返回和 print(__builtins__.__dict__)
一样的值。
其四, del
可以在 __main__
的命名空间删掉对库的引用 ,比如 import sys
后再 del sys
, 您会发现 sys
库又访问不到了,可谓变相的 unimport. 那么 del __builtins__
会发生什么? 您在 __main__
模块的 global 命名空间删掉了对 __builtins__
的引用**,您自然没法用 int
, str
之类的内置对象了,而且连 import builtins as __builtins__
之类的补救方案都执行不了,因为连 __import__
都找不到了。何等丧心病狂的 hack!
其五, from a import *
应该会直接把 a
模块所有 global 变量归属到 __main__
模块的 global 命名空间,但是您如果试试输出模块 m
里的 b
函数所属的模块名的话,即 print(b.__module__)
, 它返回 m
而不是 __main__
; 此外,如果 m
和 n
各有函数 b
, 那先后 from m import *
from n import *
的话,那 print(b.__module__)
返回 n
. 我把这灾难叫做 作用域偏移 。
最后总结编程规范:
from * import *
形式的语句 。 __builtins__
. del
来 umimport 库。 str_
. Written with StackEdit .