Python基础手册29——包
六、包
在 Python 中,导入除了可以指定模块名之外,也可以指定目录路径。为了帮助组织模块并提供命名层次结构,Python 有一个概念:包。
你可以将程序包视为文件系统上的目录,将模块视为目录中的文件,但是包和模块并不需要源自文件系统。像文件系统目录一样,包是以分层方式组织的,包本身可以包含子包以及常规模块。事实上,包导入是把计算机上的目录编成另一个 Python 的命名空间,而包对象的属性则对应于目录中所包含的子目录和模块文件。
Python 中只有一种模块类型,不管这个模块是否是用 Python、C,或者其他语言实现。包是一种特殊的模块,或者换句话说,所有的包都是模块,但不是所有的模块都是包。具体来说,包含 __path__ 属性的任何模块都被视为包。
所有模块都有一个名称,子包名称(或者文件模块名)与其父包名称之间用点号 .
分隔,类似于 Python 的标准属性访问语法。使用标准的 import 和 from-import 语句导入包中的模块。您可能有一个名为 sys 的模块和一个名为 email 的软件包,其中包含一个名为 email.mime 的子包,还有名为 email.mime.text 的子包。
包提供的层次结构对于组织大型系统内的文件会很方便,而且可以简化模块搜索路径的设置。当多个同名的程序文件安装在某一机器上时,包导入也可以偶尔来解决导入的不确定性。
包是一个有层次的文件目录结构,它定义了一个由 模块 和 子包 组成的 Python 应用程序执行环境。Python 1.5 加入了包,用来帮助解决如下问题:
- 为平坦的名称空间加入有层次的组织结构
- 允许程序员把有联系的模块组合到一起
- 允许分发者使用目录结构而不是一大堆混乱的文件
- 帮助解决有冲突的模块名称
Regular packages
Python 定义了两种类型的包,regular packages 和 namespace packages。
常规包是传统包,常规包通常实现为包含 __init__.py 文件的目录。当导入常规包时,这个 __init__.py 文件被隐式执行,它定义的对象被绑定到包名称空间中的名称。__init__.py 文件可以包含与任何其他模块可以包含的相同的 Python 代码,Python 会在导入时为模块添加一些其他属性。
例如,以下文件系统布局定义具有三个子包的顶层 parent 包:导入 parent.one 将隐式执行 parent/__init__.py 和 parent/one/__init__.py 。随后导入 parent.two 或 parent.three 将执行 parent/two/__init__.py 和 parent/three/__init__.py。
下面的章节主要介绍常规包的概念。
Namespace packages
命名空间包是各种 portions 的组合,其中每个部分都向父包提供子包。其可以存在于文件系统上的不同位置,也可以在 zip 文件,网络或 Python 在导入期间搜索的任何其他位置找到。命名空间包可以或可以不直接对应于文件系统上的对象;它们可以是没有具体表示的虚拟模块。
命名空间包不为其 __path__ 属性使用普通列表。它们改为使用自定义可迭代类型,如果其父包的路径发生变化(或对于顶级包的 sys.path),它将在该包中的下一次导入尝试时自动执行对包部分的新搜索变化。
使用命名空间包,没有 parent/__init__.py 文件。实际上,在导入搜索期间可能会有多个 parent,其中每个目录由不同的部分提供。因此,parent/one 可能不在物理上位于parent/two 旁边。在这种情况下,Python 将为顶层 parent 包创建一个命名空间包,只要它或其中一个子包被导入。
1、包导入基础
在 import 语句中列举简单文件名的地方,可以改成列出路径的名称,彼此以点号相隔:
import dir1.dir2.mod
form 语句也是一样的:
from dir1.dir2.mod import x
这些语句的点号 .
路径是对应于物理机上的目录层次的路径,通过这个路径可以获得到文件 mod.py(或类似文件,扩展名可能会有变化)。也就是说,上面的语句是表明了机器上有个目录 dir1,而 dir1 里面有子目录 dir2,而 dir2 内包含有一个名为 mod.py (或其他格式文件)的模块文件。
这些导入意味着,dri1 目录位于某个目录 dir0 中,这个 dir0 目录可以在 Python 模块搜索路径中找到。容器目录 dir0 需要添加在模块搜索路径中(除非这是顶层文件的主目录)。
dir0/dir1/dir2/mod.py
import 语句中的目录路径只能是以点号间隔的变量。选择点号语法,一是考虑到跨平台,同时也是因为 import 语句中的路径会变成实际的嵌套对象的路径。这种语法也意味着,如果你忘记了在 import 语句中省略 .py,就会得到奇怪的错误信息。
__init__.py 包文件
如果选择使用包导入,就必须多遵循一条约束:包导入语句的路径中的每个目录内都必须有 __init__.py 这个文件,否则导入包会失败。
也就是在 dir0/dir1/dir2/mod.py 的路径下必须保证在 dir1 和 dir2 中必须都含所有一个__init__.py 文件;dir0 是容器,不需要 __init__.py 文件,如果有的话,这个文件也会被忽略,dir0(而非dir0/dir1)必须列在模块搜索路径上。
__init__.py 可以包含 Python 程序代码,就像普通模块文件。这类文件从某种程度上将就像是 Python 的一种声明,尽管如此,也可以完全是空的。作为声明,这些文件可以防止有相同名称的目录不小心隐藏在模块搜索路径中,而之后才出现真正所需要的模块。没有这层保护,Python可能会搜索到和程序代码无关的目录,只是因为有一个同名的目录刚好出现在搜索路径上位置较前的目录内。
__init__.py 文件扮演了包初始化的钩子,替目录产生模块命名空间以及使用目录导入时实现 from * (也就是from ... import *)行为的角色。
1、包的初始化
Python 首次导入某个目录时,会自动执行该目录下 __init__.py 文件中的所有程序代码。因此,这类文件自然就是放置包内文件所需要初始化的代码的场所。例如,包可以使用其初始化文件,来创建所需要的数据文件、连接数据库等。
2、模块命名空间的初始化
在包导入的模型中,脚本内的目录路径,在导入后会成真实的嵌套对象路径。导入的包对应的目录内的 __init_.py 文件会在导入时执行,其生成的所有变量名都会成为生成的模块对象的属性。__init__.py 文件为目录所创建的包模块对象提供了命名空间。
3、from * 语句的行为
作为一个高级功能,你可以在 __init__.py 文件内使用 __all__ 列表来定义目录以 from * 语句形式导入时,需要导出什么。在 __init__.py 文件中,__all__ 列表是指当包(目录)名称使用 from * 的时候,应该导入的子模块的名称清单。如果没有设定 __all__ ,from * 语句不会自动加载嵌套于该目录内的子模块。取而代之的是,只加载该目录的 __init__.py 文件中赋值语句定义的变量名,包括改文件中程序代码明确导入的任何子模块。
2、包导入实例
当 Python 向下搜索路径时,import 语句会在每个目录首次遍历时,执行该目录的初始化文件。假定有如下的目录结构: phone 是最顶层的包,voicedta 等是它的子包。 我们可以这样导入子包: 你也可使用 from-import 语句直接导入最底层的模块或模块对象的属性:执行了上面的导入操作后,路径中的每个目录名称都会变成赋值了包模块对象的属性,而包模块对象的命名空间则是由该目录内的 init.py 文件中所有赋值语句进行初始化的。
包对应的 form 语句和 import 语句
import 语句和包一起使用时,有些不方便,因为你必须经常在程序中重新输入路径。 因此,让包使用 from 语句来避免每次读取时都得重新输入路径。更重要的是,如果你重新改变目录树结构,from 语句只需在程序代码中更新一次路径,而 import 则需要修改很多地方。为什么使用包导入
包让导入更具信息性,并可以作为组织工具,简化模块的搜索路径,而且可以解决模糊性。
首先,因为包导入提供了程序文件的目录信息,因此可以轻松地找到文件,从而可以作为组织工具来使用。没有包导入时,通常得通过查看模块搜索路径才能找出文件。再者,如果根据功能把文件组织成子目录,包导入会让模块扮演的角色更为明显,也使代码更具可读性。 和下面包含路径的导入相比,提供的信息回显的更少。包导入也可以大幅简化 PYTHONPATH 和 .pth 文件搜索路径设置。实际上,如果所有跨目录的导入,都是用包导入,并且让这些包导入都相对于一个共同的根目录,把所有 Python 的程序代码都存在其中,在搜索路径上就只需一个单独的接入点:通用的根目录。包导入让你想导入的文件更明确,从而解决了模糊性。
3、包的相对导入
在包自身的内部的文件中导入包中的模块可以使用和外部导入相同的路径语法,但是,它们也可能使用特殊的包内搜索规则来简化导入语句。也就是说,包内的导入可能相对于包,而不是列出完整的包导入路径。而且,当文件中导入的模块的对应文件出现在模块搜索路径上许多地方时,可以解决模糊性。
相对导入基础知识
我们可以在 from 语句 后通过使用点号(.)来指定当前目录,这可以导入位于同一包中的模块(所谓的包相对导入),而不是位于模块导入搜索路径上某处的模块(叫做绝对导入)。相对导入的点号用来表示当前文件的目录。前面再增加一个点号,将执行从当前文件目录的父目录相对导入。 在 Python3.0 中,不带点号的一个 import 总是会引发 Python 在 sys.path 所包含的目录中查找。下面的例子,第一个 import 语句成功导入,第二个 import 导入失败。点号只可以用来对 from 语句执行强制相对导入,而不能对 import 语句这样。前面没有点号的 from 语句与 import 语句的行为相同。
4、模块查找规则总结
简单模块名通过搜索 sys.path 路径列表上的每个目录来查找,从左到右进行。这个列表由系统默认设置和用户配置设置组成。
包是带有一个特殊的 __init__.py 文件的目录,这使得一个导入中可以使用 A.B.C 目录路径语法。
在一个包文件中,常规的 import、from 语句使用和其他地方的导入一样的 sys.path 搜索规则。包中的相对导入使用 from 语句以及后面的点号,它是相对于包的;也就是说,只检查包目录,并且不使用常规的 sys.path 查找。
《Python基础手册》系列:
Python基础手册 1 —— Python语言介绍
Python基础手册 2 —— Python 环境搭建(Linux)
Python基础手册 3 —— Python解释器
Python基础手册 4 —— 文本结构
Python基础手册 5 —— 标识符和关键字
Python基础手册 6 —— 操作符
Python基础手册 7 —— 内建函数
Python基础手册 8 —— Python对象
Python基础手册 9 —— 数字类型
Python基础手册10 —— 序列(字符串)
Python基础手册11 —— 序列(元组&列表)
Python基础手册12 —— 序列(类型操作)
Python基础手册13 —— 映射(字典)
Python基础手册14 —— 集合
Python基础手册15 —— 解析
Python基础手册16 —— 文件
Python基础手册17 —— 简单语句
Python基础手册18 —— 复合语句(流程控制语句)
Python基础手册19 —— 迭代器
Python基础手册20 —— 生成器
Python基础手册21 —— 函数的定义
Python基础手册22 —— 函数的参数
Python基础手册23 —— 函数的调用
Python基础手册24 —— 函数中变量的作用域
Python基础手册25 —— 装饰器
Python基础手册26 —— 错误 & 异常
Python基础手册27 —— 模块
Python基础手册28 —— 模块的高级概念
Python基础手册29 —— 包