转载

Python 作用域解析之补遗

有人把 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__ ; 此外,如果 mn 各有函数 b , 那先后 from m import * from n import * 的话,那 print(b.__module__) 返回 n . 我把这灾难叫做 作用域偏移

最后总结编程规范:

  1. 禁止一切 from * import * 形式的语句
  2. 不要乱动一切以下划线开头的东西,自然包括 __builtins__ .
  3. 通过 del 来 umimport 库。
  4. 不要在 local 或 global 空间命名和 builtin 空间某对象重复的 identifier, 可以在名字后面加一个下划线以区分,比如 str_ .

Written with StackEdit .

原文  http://tech.acgtyrant.com/Python-作用域解析之补遗/
正文到此结束
Loading...