12.3 名称空间
名称空间是名称(标识符)到对象的映射。向名称空间添加名称的操作过程涉及绑定标识符到指定对象的操作(以及给该对象的引用计数加1) 。《Python语言参考手册》(Python Language Reference)有如下的定义:改变一个名字的绑定叫做重新绑定,删除一个名字叫做解除绑定。
我们在第11章已经介绍过在执行期间有两个或三个活动的名称空间。这三个名称空间分别是局部名称空间,全局名称空间和内建名称空间,但局部名称空间在执行期间是不断变化的,所以我们说“两个或三个”。从名称空间中访问这些名字依赖于它们的加载顺序,或是系统加载这些名称空间的顺序。
Python解释器首先加载内建名称空间。它由builtins模块中的名字构成。随后加载执行模块的全局名称空间,它会在模块开始执行后变为活动名称空间。这样我们就有了两个活动的名称空间。
核心笔记:builtins和builtin
builtins模块和builtin模块不能混淆。虽然它们的名字相似——尤其对于新手来说。builtins模块包含内建名称空间中内建名字的集合。其中大多数(如果不是全部的话)来自builtin模块,该模块包含内建函数,异常以及其他属性。在标准Python执行环境下,builtins包含builtin的所有名字。Python曾经有一个限制执行模式,允许你修改builtins,只保留来自builtin的一部分,创建一个沙盒(sandbox)环境。但是,因为它有一定的安全缺陷,而且修复它很困难,Python已经不再支持限制执行模式。(如版本2.3)
如果在执行期间调用了一个函数,那么将创建出第三个名称空间,即局部名称空间。我们可以通过globals()和locals()内建函数判断出某一名字属于哪个名称空间。我们将在本章后面详细介绍这两个函数。
12.3.1 名称空间与变量作用域比较
好了,我们已经知道了什么是名称空间,那么它与变量作用域有什么关系呢?它们看起来极其相似,事实上也确实如此。
名称空间是纯粹意义上的名字和对象间的映射关系,而作用域还指出了从用户代码的哪些物理位置可以访问到这些名字。图12-1展示了名称空间和变量作用域的关系。
图 12-1 名称空间和变量作用域
注意每个名称空间是一个自我包含的单元。但从作用域的观点来看,事情是不同的。所有局部名称空间的名称都在局部作用范围内。局部作用范围以外的所有名称都在全局作用范围内。
还要记得在程序执行过程中,局部名称空间和作用域会随函数调用而不断变化,而全局名称空间是不变的。
学完这一节后,我们建议读者在遇到名称空间的时候想想“它存在吗?”,遇到变量作用域的时候想想“我能看见它吗?”
12.3.2 名称查找、确定作用域、覆盖
那么确定作用域的规则是如何联系到名称空间的呢?它所要做的就是名称查询。访问一个属性时,解释器必须在三个名称空间中的一个找到它。首先从局部名称空间开始,如果没有找到,解释器将继续查找全局名称空间。如果这也失败了,它将在内建名称空间里查找。如果最后的尝试也失败了,你会得到这样的错误:
这个错误信息体现了先查找的名称空间是如何“遮蔽”其他后搜索的名称空间的。这体现了名称覆盖的影响。图12-1的灰盒子展示了遮蔽效应。例如,局部名称空间中找到的名字会隐藏全局或内建名称空间的对应对象。这就相当于“覆盖”了那个全局变量。请参阅前面章节引入的这几行代码:
执行代码,我们将得到这样的输出:
foo()函数局部名称空间里的bar变量覆盖了全局的bar变量。虽然bar存在于全局名称空间里,但程序首先找到的是局部名称空间里的那个,所以“覆盖”了全局的那个。关于作用域的更多内容请参阅第11.8节。
12.3.3 无限制的名称空间
Python的一个有用的特性在于你可以在任何需要放置数据的地方获得一个名称空间。我们已经在前一章见到了这一特性,你可以在任何时候给函数添加属性(使用熟悉的句点属性标识)。
在本章,我们展示了模块是如何创建名称空间的,你也可以使用相同的方法访问它们:
虽然我们还没介绍面向对象编程(OOP,将在第13章介绍),但我们可以看看一个简单的“Hello World!”例子:
你可以把任何想要的东西放入一个名称空间里。像这样使用一个类(实例)是很好的,你甚至不需要知道一些关于OOP的知识(注:类似这样的变量叫做实例属性)。不管名字如何,这个实例只是被用做一个名称空间。
随着学习的深入,你会发现OOP是多么地有用,比如在运行时临时(而且重要)变量的时候!正如在《Zen of Python》中陈述的最后一条,“名称空间是一个响亮的杰出创意——那就让我们多用用它们吧!”(在交互模式解释器下导入this模块就可以看到完整的《Zen》)。