5.2. 包

Python 只有一种模块对象类型,所有模块都属于该类型,无论模块是用 Python、C 还是别的语言实现。 为了帮助组织模块并提供名称层次结构,Python 还引入了 的概念。

你可以把包看成是文件系统中的目录,并把模块看成是目录中的文件,但请不要对这个类似做过于字面的理解,因为包和模块不是必须来自于文件系统。 为了方便理解本文档,我们将继续使用这种目录和文件的类比。 与文件系统一样,包通过层次结构进行组织,在包之内除了一般的模块,还可以有子包。

要注意的一个重点概念是所有包都是模块,但并非所有模块都是包。 或者换句话说,包只是一种特殊的模块。 特别地,任何具有 path 属性的模块都会被当作是包。

所有模块都有自己的名字。 子包名与其父包名以点号分隔,与 Python 的标准属性访问语法一致。 例如你可能看到一个名为 sys 的模块,以及一个名为 email 的包,这个包又有一个名为 email.mime 的子包和该子包中的名为 email.mime.text 的子包。

5.2.1. 常规包

Python 定义了两种类型的包,常规包命名空间包。 常规包是传统的包类型,它们在 Python 3.2 及之前就已存在。 常规包通常以一个包含 init.py 文件的目录形式实现。 当一个常规包被导入时,这个 init.py 文件会隐式地被执行,它所定义的对象会被绑定到该包命名空间中的名称。init.py 文件可以包含与任何其他模块中所包含的 Python 代码相似的代码,Python 将在模块被导入时为其添加额外的属性。

例如,以下文件系统布局定义了一个最高层级的 parent 包和三个子包:

  1. parent/
  2. __init__.py
  3. one/
  4. __init__.py
  5. two/
  6. __init__.py
  7. three/
  8. __init__.py

导入 parent.one 将隐式地执行 parent/init.pyparent/one/init.py。 后续导入 parent.twoparent.three 则将分别执行 parent/two/init.pyparent/three/init.py

5.2.2. 命名空间包

命名空间包是由多个 部分 构成的,每个部分为父包增加一个子包。 各个部分可能处于文件系统的不同位置。 部分也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。 命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。

命名空间包的 path 属性不使用普通的列表。 而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) 发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。

命名空间包没有 parent/init.py 文件。 实际上,在导入搜索期间可能找到多个 parent 目录,每个都由不同的部分所提供。 因此 parent/one 的物理位置不一定与 parent/two 相邻。 在这种情况下,Python 将为顶级的 parent 包创建一个命名空间包,无论是它本身还是它的某个子包被导入。

另请参阅 PEP 420 了解对命名空间包的规格描述。