6.4. 包
包是一种通过用“带点号的模块名”来构造 Python 模块命名空间的方法。 例如,模块名 A.B
表示 A
包中名为 B
的子模块。正如模块的使用使得不同模块的作者不必担心彼此的全局变量名称一样,使用加点的模块名可以使得 NumPy 或 Pillow 等多模块软件包的作者不必担心彼此的模块名称一样。
假设你想为声音文件和声音数据的统一处理,设计一个模块集合(一个“包”)。由于存在很多不同的声音文件格式(通常由它们的扩展名来识别,例如:.wav
, .aiff
, .au
),因此为了不同文件格式间的转换,你可能需要创建和维护一个不断增长的模块集合。 你可能还想对声音数据还做很多不同的处理(例如,混声,添加回声,使用均衡器功能,创造人工立体声效果), 因此为了实现这些处理,你将另外写一个无穷尽的模块流。这是你的包的可能结构(以分层文件系统的形式表示):
- sound/ Top-level package
- __init__.py Initialize the sound package
- formats/ Subpackage for file format conversions
- __init__.py
- wavread.py
- wavwrite.py
- aiffread.py
- aiffwrite.py
- auread.py
- auwrite.py
- ...
- effects/ Subpackage for sound effects
- __init__.py
- echo.py
- surround.py
- reverse.py
- ...
- filters/ Subpackage for filters
- __init__.py
- equalizer.py
- vocoder.py
- karaoke.py
- ...
当导入这个包时,Python搜索 sys.path
里的目录,查找包的子目录。
必须要有 init.py
文件才能让 Python 将包含该文件的目录当作包。 这样可以防止具有通常名称例如 string
的目录在无意中隐藏稍后在模块搜索路径上出现的有效模块。 在最简单的情况下,init.py
可以只是一个空文件,但它也可以执行包的初始化代码或设置 all
变量,具体将在后文介绍。
包的用户可以从包中导入单个模块,例如:
- import sound.effects.echo
这会加载子模块 sound.effects.echo
。但引用它时必须使用它的全名。
- sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
导入子模块的另一种方法是
- from sound.effects import echo
这也会加载子模块 echo
,并使其在没有包前缀的情况下可用,因此可以按如下方式使用:
- echo.echofilter(input, output, delay=0.7, atten=4)
另一种形式是直接导入所需的函数或变量:
- from sound.effects.echo import echofilter
同样,这也会加载子模块 echo
,但这会使其函数 echofilter()
直接可用:
- echofilter(input, output, delay=0.7, atten=4)
请注意,当使用 from package import item
时,item可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数,类或变量。 import
语句首先测试是否在包中定义了item;如果没有,它假定它是一个模块并尝试加载它。如果找不到它,则引发 ImportError
异常。
相反,当使用 import item.subitem.subsubitem
这样的语法时,除了最后一项之外的每一项都必须是一个包;最后一项可以是模块或包,但不能是前一项中定义的类或函数或变量。
6.4.1. 从包中导入 *
当用户写 from sound.effects import *
会发生什么?理想情况下,人们希望这会以某种方式传递给文件系统,找到包中存在哪些子模块,并将它们全部导入。这可能需要很长时间,导入子模块可能会产生不必要的副作用,这种副作用只有在显式导入子模块时才会发生。
唯一的解决方案是让包作者提供一个包的显式索引。import
语句使用下面的规范:如果一个包的 init.py
代码定义了一个名为 all
的列表,它会被视为在遇到 from package import
时应该导入的模块名列表。在发布该包的新版本时,包作者可以决定是否让此列表保持更新。包作者如果认为从他们的包中导入 的操作没有必要被使用,也可以决定不支持此列表。例如,文件 sound/effects/init.py
可以包含以下代码:
- __all__ = ["echo", "surround", "reverse"]
这意味着 from sound.effects import *
将导入 sound
包的三个命名子模块。
如果没有定义 all
,from sound.effects import *
语句 不 会从包 sound.effects
中导入所有子模块到当前命名空间;它只确保导入了包 sound.effects
(可能运行任何在 init.py
中的初始化代码),然后导入包中定义的任何名称。这包括 init.py`
定义的任何名称(以及显式加载的子模块)。它还包括由之前的 import
语句显式加载的包的任何子模块。思考下面的代码:
- import sound.effects.echo
- import sound.effects.surround
- from sound.effects import *
在这个例子中, echo
和 surround
模块是在执行 from…import
语句时导入到当前命名空间中的,因为它们定义在 sound.effects
包中。(这在定义了 all
时也有效。)
虽然某些模块被设计为在使用 import *
时只导出遵循某些模式的名称,但在生产代码中它仍然被认为是不好的做法。
请记住,使用 from package import specific_submodule
没有任何问题! 实际上,除非导入的模块需要使用来自不同包的同名子模块,否则这是推荐的表示法。
6.4.2. 子包参考
当包被构造成子包时(与示例中的 sound
包一样),你可以使用绝对导入来引用兄弟包的子模块。例如,如果模块 sound.filters.vocoder
需要在 sound.effects
包中使用 echo
模块,它可以使用 from sound.effects import echo
。
你还可以使用import语句的 from module import name
形式编写相对导入。这些导入使用前导点来指示相对导入中涉及的当前包和父包。例如,从 surround
模块,你可以使用:
- from . import echo
- from .. import formats
- from ..filters import equalizer
请注意,相对导入是基于当前模块的名称进行导入的。由于主模块的名称总是 "main"
,因此用作Python应用程序主模块的模块必须始终使用绝对导入。
6.4.3. 多个目录中的包
包支持另一个特殊属性, path
。它被初始化为一个列表,其中包含在执行该文件中的代码之前保存包的文件 init.py
的目录的名称。这个变量可以修改;这样做会影响将来对包中包含的模块和子包的搜索。
虽然通常不需要此功能,但它可用于扩展程序包中的模块集。
脚注
- 1
- 实际上,函数定义也是“被执行”的“语句”;模块级函数定义的执行在模块的全局符号表中输入该函数名。