4-3-模块包
除了模块名之外,导入也可以指定目录路径。Python 代码的目录就称为包,因此,这类导入就称为包导入。
1. 包导入基础
在 import 语句中可以列出路径名称,彼此以点号相隔。
import dir1.dir2.mod
通过这个路径可以获得 dir1 目录中的 dir2 目录中的 mod.py 模块文件。
1.1 包和搜索路径设置
import 语句中的目录路径只能是以点号间隔的变量。不能在 import 语句中使用任何平台特定的路径语法。需要在模块搜索路径设置中使用平台特定的语法,来定义容器的目录。
如果完整路径是 C:\mycode\dir1\dir2\mod.py
,则可以增加 C:\mycode
在 PYTHONPATH 系统变量中或是 .pth 文件中。
1.2 __init__.py
包文件
包导入语句的路径中的每个目录内都必须有 __init__.py
这个文件,否则导入包会失败。
对于如下目录结构:
dir0\dir1\dir2\mod.py
以及这种形式的 import 语句:
import dir1.dir2.mod
必须遵循下列规则:
- dir1 和 dir2 中必须都含有一个
__init.py__
文件。 - dir0 是容器,不需要
__init__.py
文件;如果有的话,这个文件也会被忽略。 - dir0(而非 dir0\dir1)必须列在模块搜索路径上。
结果就是,这个目录结构应该是这样:
dir0\
dir1\
__init__.py
dir2\
__init__.py
mod.py
包初始化文件角色__init__.py
文件扮演了包初始化的 hook、替目录产生模块命名空间以及使用目录导入时实现 from * 行为的角色:
- **包的初始化。**Python 首次导入某个目录时,会自动执行该目录下
__init__.py
文件中的所有程序代码。 - **模块命名空间的初始化。**在包导入的模型中,脚本内的目录路径,在导入后会变成真实的嵌套对象路径。
- **from * 语句的行为。**可以在
__init__.py
文件内使用__all__
列表来定义目录以 from * 语句形式导入时需要导出什么。如果没有设定__all__
,from * 语句不会自动加载嵌套于该目录内的子模块。而是只加载该目录的__init__.py
文件中赋值语句定义的变量名,包括该文件中程序代码明确导入的任何子模块。
__init__.py
可以为空,但必须存在。
如果 __init__.py
文件包含如下内容:
# pkg/__init__.py
print(f'Invoking __init__.py for {__name__}')
A = ['quux', 'corge', 'grault']
当导入包时,全局列表 A 会初始化:
>>> import pkg
Invoking __init__.py for pkg
>>> pkg.A
['quux', 'corge', 'grault']
包中的模块也可以对全局 A 进行访问:
def foo():
from pkg import A
print('[mod1] foo() / A = ', A)
>>> from pkg import mod1
Invoking __init__.py for pkg
>>> mod1.foo()
[mod1] foo() / A = ['quux', 'corge', 'grault']
__init__.py
还可以用于从包中自动导入模块。如果 __init__.py
在 pkg 目录中包含如下内容:
import pkg.mod1, pkg.mod2
那么当执行 import pkg 时,模块 mod1 和 mod2 会自动导入。
2. 从包中使用 * 导入
当一个包中有多个文件时,当使用 from <pakage_name> import *
时,不会导入任何模块。Python 遵循这样的约定:如果 __init__.py
文件在包目录中包含一个名为 __all__
的列表,则它被认为是当遇到 from <pakage_name> import *
的语句时应该导入的模块列表。
# pkg/__init__.py
__all__ = [
'mod1',
'mod2',
'mod3',
'mod4'
]
from pkg import *
将会导入所有模块。
__all__
也可以在模块中定义,其作用是相同的:控制使用 import *
导入的内容。
总之,在指定 import *
时,包和模块都使用 __all__
来控制导入的内容。但默认行为不同:
- 对于一个包,当
__all__
未被定义时,import *
不导入任何内容。 - 对于一个模块,当
__all__
未被定义时,import *
导入所有内容(除了以下划线开头的名称)。
3. 包相对导入
3.1 相对导入基础知识
from 语句可以使用前面的点号来指定,它们需要位于同一包中的模块(相对导入),而不是位于模块导入搜索路径上某处的模块(绝对导入):
- **使用点号导入。**这样的导入将只在包的内部搜索,并不会搜索位于搜索路径上某处的同名模块。直接效果是包模块覆盖了外部的模块。
- **不使用点号导入。**在一个包中导入默认是绝对路径的,导入忽略了包含包自身并在 sys.path 路径上的某处查找。
from . import spam
语句告诉 Python 把位于与语句中给出的文件相同包路径中的名为 spam 的一个模块导入。
from .spam import name
语句意味着从名为 spam 的模块导入变量 name,而这个 spam 模块与包含这条语句的文件位于同一个包下。
import string
总是在 sys.path 上的某处查找一个 string 模块,而不是查找该包中具有相同名称的模块。
其他的基于点的相对引用模式也是可能的。在位于名为 mypkg 的一个包目录中的一个模块文件中,如下替代的 import 形式也像所述的那样工作:
from .string import name1, name2 # 从 mypkg.string 中导入 names
from . import string # 导入 mypkg.string
from .. import string # 导入 mypkg 的 string 兄弟
相对导入中的“.”用来表示包含文件的包目录,而导入就出现在该文件中。前面再加一个点,将执行从当前包的父目录的相对导入。
位于某个模块 A.B.C 中的代码可以做下面任何一种导入:
from . import D # 导入 A.B.D(.代表 A.B)
from ..import E # 导入 A.E(..代表 A)
from .D import X # 导入 A.B.D.X(.代表 A.B)
from ..E import X # 导入 A.E.X(..代表 A)
3.2 相对导入的作用域
- 相对导入适用于只在包内导入。
- 相对导入只适用于 from 语句。
3.3 模块查找规则总结
- 简单模块名(例如,A)通过搜索 sys.path 路径列表上的每个目录来查找,从左到右进行。这个列表由系统默认设置和用户配置设置组成。
- 包是带有一个特殊的
__init__py.
文件的 Python 模块的直接目录,这使得一个导入中可以使用 A.B.C 目录路径语法。在 A.B.C 的一条导入中,名为 A 的目录位于相对于 sys.path 的常规模块导入搜索,B 是 A 中的另一个包子目录,C 是一个模块或 B 中的其他可导入项。 - 在一个包文件中,常规的 import 和 from 语句使用和其他地方的导入一样的 sys.path 搜索规则。包中的导入使用 from 语句以及前面的点号,然而,它是相对于包的;也就是说,只检查包目录,并且不使用常规的 sys.path 查找。
4. Python 3.3 的命名空间包
Python 3.3 之后有四种导入模型:
- **基本模块导入:**import mod, from mod import attr
- **包导入:**import dir1.dir2.mod, from dir1.mod import attr
- **包相对导入:**from . import mod(相对), import mod(绝对)
- **命名空间包:**import splitdir.mod,允许包扩展多个目录,不需要初始化文件。
4.1 命名空间包语义
import 算法
- 如果找到
directory\spam\__init__.py
文件,常规包被导入和返回。 - 如果找到
directory\spam.{py, pyc, 或其他模块扩展}
,一个简单模块被导入和返回。 - 如果找到
directory\spam
并且是目录,则被记录并且在搜索路径的下一个目录继续扫描。 - 如果上述都未找到,则在搜索路径的下一个目录继续扫描。
如果搜索路径扫描完成,没有在步骤1或步骤2中返回模块或包,在步骤3中记录到至少一个目录,则命名空间包被创建。新的命名空间包有一个 __path__
属性,包含找到的目录路径字符串,在步骤3的扫描中记录。一旦命名空间包被创建,和传统包就没有功能上的不同。
4.2 对传统包的影响:可选的 __init__.py
文件
Python 3.3 的包不再需要 __init__.py
文件——当单个目录包没有这个文件,则会被当做一个单目录命名空间包。许多包不需要初始化代码。传统包模型依旧完全支持,自动运行 __init__.py
文件中的代码。