1. 概述
模块支持从逻辑上组织Python代码。当代码量变得相当大的时候,最好把代码分成一些有组织的代码段,前提是保证它们的彼此交互。那些自我包含并且有组织的代码片段就是模块(module)。
2. 模块名称空间
从基本概念来说, 一个名称空间就是一个从名称到对象的关系映射集合。给定一个模块名之后, 只可能有一个模块被导入到 Python 解释器中。在不同模块间不会出现名称交叉现象,每个模块都定义了它自己的唯一的名称空间。
3. 搜索路径和路径搜索
模块的导入需要一个叫做"路径搜索"的过程。即在文件系统"预定义区域"中查找 *.py 文件。这些预定义区域只是你的 Python 搜索路径的集合。路径搜索和搜索路径是两个不同的概念,前者是查找某个文件的操作,后者是去查找一组目录。有时候导入模块操作会失败:
>>> import xxx
Traceback (innermost last):
File "<interactive input>", line 1, in ?
ImportError: No module named xxx
发生这样的错误时, 解释器会告诉你它无法访问请求的模块,可能的原因是模块不在搜索路径里, 从而导致了路径搜索的失败。
默认搜索路径是在编译或是安装时指定的。它可以在一个或两个地方修改。
启动 Python 的 shell 或命令行的 PYTHONPATH 环境变量。该变量的内容是一组用冒号分割的目录路径。如果你想让解释器使用这个变量, 那么请确保在启动解释器或执行 Python 脚本前设置或修改了该变量。 解释器启动之后, 也可以访问这个搜索路径, 它会被保存在 sys 模块的 sys.path 变量里。不过它已经不是冒号分割的字符串, 而是包含每个独立路径的列表。切记, 搜索路径在不同系统下一般是不同的。
>>> sys.path
['', '/usr/lib64/python27.zip', '/usr/lib64/python2.7', '/usr/lib64/python2.7/plat-linux2', '/usr/lib64/python2.7/lib-tk', '/usr/lib64/python2.7/lib-old', '/usr/lib64/python2.7/lib-dynload', '/usr/lib64/python2.7/site-packages', '/usr/lib/python2.7/site-packages']
sys.path 变量只是个列表, 所以我们可以随时随地对它进行修改。如果某个模块不在搜索路径里,那么只需要调用列表的 append() 方法即可。
sys.path.append('/home/wesc/py/lib')
某个模块有可能有很多拷贝。这时,解释器会沿用路径搜索顺序找到的第一个模块。
使用 sys.modules 可以找到当前导入了哪些模块和它们来自什么地方。和sys.path 不同, sys.modules 是一个字典, 使用模块名作为键( key),对应物理地址作为值( value )。
4. 名称空间
名称空间是名称(标识符)到对象的映射。向名称空间添加名称的操作过程涉及到绑定标识符到指定对象的操作(以及给该对象的引用计数加 1 )。 Python 的名称(Name)是对象的一个标识(Identifier)。在Python里面一切皆对象,名称就是用来引用对象的。
执行期间会有两个或三个活动的名称空间。这三个名称空间分别是局部名称空间,全局名称空间和内建名称空间。但局部名称空间在执行期间是不断变化的, 所以我们说"两个或三个"。
Python 解释器首先加载内建名称空间。 它由__builtins__模块中的名字构成。 随后加载执行模块的全局名称空间, 它会在模块开始执行后变为活动名称空间。这样我们就有了两个活动的名称空间。
__builtins__模块和__builtin__模块不能混淆。__builtins__模块包含内建名称空间中内建名字的集合。其中大多数来自__builtin__模块,该模块包含内建函数,异常以及其他属性。
如果在执行期间调用了一个函数, 那么将创建出第三个名称空间,即局部名称空间。 我们可以通过 globals() 和 locals()内建函数判断出某一名字属于哪个名称空间。
名称空间与变量作用域比较
名称空间是纯粹意义上的名字和对象间的映射关系,而作用域还指出了从用户代码的哪些物理位置可以访问到这些名字。
注意每个名称空间是一个自我包含的单元。但从作用域的观点来看, 事情是不同的. 所有局部名称空间的名称都在局部作用范围内。局部作用范围以外的所有名称都在全局作用范围内。还要记得在程序执行过程中,局部名称空间和作用域会随函数调用而不断变化, 而全局名称空间是不变的。
名称查找, 确定作用域, 覆盖
那么确定作用域的规则是如何联系到名称空间的呢? 它所要做的就是名称查询. 访问一个属性时, 解释器必须在三个名称空间中的一个找到它。 首先从局部名称空间开始, 如果没有找到, 解释器将继续查找全局名称空间. 如果这也失败了, 它将在内建名称空间里查找。 如果最后的尝试也失败了, 你会得到错误 :
>>> foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
这个错误体现了,先查找的名称空间是如何“屏蔽”其他后搜索的名称空间的。这体现了名称覆盖的影响。上面图中的灰盒子展示了遮蔽效应。例如,局部名称空间中找到的名字会隐藏全局或内建名称空间的对应对象。这就相当于“覆盖”了那个全局变量。例如:
def foo():
bar = 200
print("in foo,bar is",bar)
bar = 100
print("in main,bar is",bar)
foo()
输出结果:
in main,bar is 100
in foo,bar is 200
foo()函数局部名称空间里的bar变量覆盖了全局的bar变量。虽然bar存在域全局名称空间里,但程序首先找到的是局部名称空间里的那个,所以覆盖了全局的那一个。
5. 无限制的名称空间
Python 的一个有用的特性在于你可以在任何需要放置数据的地方获得一个名称空间。比如,可以在任何时候给函数添加属性。
def foo():
pass
foo.__doc__ = 'doc.'
foo.version = 0.2
你可以把任何想要的东西放入一个名称空间里。
[参考文献]
- 《Python 核心编程(第二版)》
- Python 名称空间与作用域 https://blog.csdn.net/lihao21/article/details/79112054