Python 作用域解析之补遗
pwpb6678
8年前
<p>有人把 Python 作用域解析规则总结为 <a href="/misc/goto?guid=4959673201630548327" rel="nofollow,noindex">LEGB</a> , 确实够精炼简要。但我还要继续补遗:</p> <p>其一,所谓 global 命名空间,都是隶属某模块名的。于是当解释执行(注意没有加 -m 参数)某 Python 源代码文件,或在交互式环境里,这个文件或交互式环境本身所谓的 global 命名空间,其实同样隶属一个模块,它的名字就叫 <a href="/misc/goto?guid=4959673201735345675" rel="nofollow,noindex">__main__</a> . 如果您在被解释执行的文件或交互式环境定义一个函数对象 a , 那么 print(a.__module__) 会返回一个 str 对象,其值为 __main__ . 说来好笑,我曾经纳闷 <strong>有什么办法能访问到这模块</strong> ,后来想想,其实我本来不就在文件和交互式环境本身里面啊,当然能随便直接访问了。</p> <p>其二, import a , print(a.b) 很好理解,会先在 __main__ 模块解析到 a 这模块的名字,接着就在后者的 global 命名空间继续解析到 b .</p> <p>其三, print(sum.__module__) , def sum(): pass , print(sum.__module__) 会先后输出 __builtins__ 和 __main__ , 但这不是因为 rebinding 了 builtins 命名空间的 sum ,而是在 __main__ 模块的 global 命名空间定义了 sum 对象且后者被先解析到而已。此外您也可以 把 __builtins__ 当成模块对象(注意不是 str 对象)来访问它 global 命名空间的值 ,比如 print(__builtins__.__dict__) 就返回一个字典对象,后者把 str 对象映射为您可以访问的内置对象。说起来 <strong>其实这里很微妙</strong> ,因为 __builtins__ 本身应该被解析为 __main__ 模块 global 命名空间的某对象了,但您并没有 import 它, 也就是说它从一开始就默认存在,但偏偏又不隶属「如同字面意义上所真正内置」的 __builtins__ 模块的 global 命名空间! 事实上您还可以显式 import builtins , 再继续访问它的属性……比如 print(builtins.__dict__) 就返回和 print(__builtins__.__dict__) 一样的值。</p> <p>其四, <strong> del 可以在 __main__ 的命名空间删掉对库的引用 </strong> ,比如 import sys 后再 del sys , 您会发现 sys 库又访问不到了,可谓变相的 unimport. <strong> 那么 del __builtins__ 会发生什么? </strong> 您在 __main__ 模块的 global 命名空间删掉了对 __builtins__ 的引用**,您自然没法用 int , str 之类的内置对象了,而且连 import builtins as __builtins__ 之类的补救方案都执行不了,因为连 __import__ 都找不到了。何等丧心病狂的 hack!</p> <p>其五, 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 . 我把这灾难叫做 <strong>作用域偏移</strong> 。</p> <p>最后总结编程规范:</p> <ol> <li><strong>禁止一切 from * import * 形式的语句 </strong> 。</li> <li>不要乱动一切以下划线开头的东西,自然包括 __builtins__ .</li> <li>通过 del 来 umimport 库。</li> <li>不要在 local 或 global 空间命名和 builtin 空间某对象重复的 identifier, 可以在名字后面加一个下划线以区分,比如 str_ .</li> </ol> <p> </p> <p>via: http://tech.acgtyrant.com/Python-作用域解析之补遗/</p>