6.4 包

包是一种通过使用 “带点的模块名” 而构成的 Python 模块的命名空间。举个例子,模块名 A.B 指定了名为 A 的包内的一个名为 B 的子模块。就像模块的使用可以把不同模块的作者从对每个其他的全局变量名的担心中拯救出来一样,带点模块名的使用把多模块包 (如 NumPy 或者 Python 图片库) 的作者从对每个其他的模块名的担心中拯救出来。

假定你要为均匀处理音频文件和音频数据设计一个模块的集合 (一个“包”)。有许许多多的音频文件格式 (通常通过扩展名识别,比如 .wav, .aiff, .au),因此你可能需要为不同文件格式之间互相转换而创建并维护一个增长的模块集合。还有很多你可能想在声音文件中执行的不同操作 (比如混音,加入回声,执行一个均衡器函数,创建一个人工立体声效果),所以,除此之外,你可以写一个永不结束的模块流用于执行这些操作。下面是一个包可行的结构 (按照分层文件系统表示出来)。

  1. sound/ Top-level package
  2. __init__.py Initialize the sound package
  3. formats/ Subpackage for file format conversions
  4. __init__.py
  5. wavread.py
  6. wavwrite.py
  7. aiffread.py
  8. aiffwrite.py
  9. auread.py
  10. auwrite.py
  11. ...
  12. effects/ Subpackage for sound effects
  13. __init__.py
  14. echo.py
  15. surround.py
  16. reverse.py
  17. ...
  18. filters/ Subpackage for filters
  19. __init__.py
  20. equalizer.py
  21. vocoder.py
  22. karaoke.py
  23. ...

导入这个包的时候,Python 会搜索 sys.path 上的目录,寻找包的子目录。

__init__.py 文件是必需的,用来让 Python 把这个目录当作包含包来处理。这用来阻止具有公有名字,比如 string,的目录无意间隐藏了有效的模块,这些模块之后在模块搜索路径中出现。在最简单的情形中,__init__.py 可以仅仅是一个空的文件,但是它仍然可以执行包的初始化代码,或者设定 __all__ 变量,我们之后再说。

包的用户可以从包中导入单个的模块个体,比如:

  1. import sound.effects.echo

这样会加载子模块 sound.effects.echo。它必须用全名来索引。

  1. sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

一种可选的导入子模块的额方法是:

  1. from sound.effects import echo

这也可以加载子模块 echo,并且可以不用包的前缀就可以使用它,所以可以像下面这么使用:

  1. echo.echofilter(input, output, delay=0.7, atten=4)

另一种变体是直接导入想要的函数或者变量:

  1. from sound.effects.echo import echofilter

又一遍,这导入了子模块 echo,但是这就使得它的函数 echofilter 可以直接被使用:

  1. echofilter(input, output, delay=0.7, atten=4)

注意,当使用 from package import item 时,这个元素可以是这个包子模块 (或者子包),或者某个其他在包中定义的名字,比如函数,类,或者变量。import 语句首先检测这个元素在包中是否定义了,如果没有,它就认为这是一个模块,然后会尝试着加载它,如果寻找失败,就会抛出一个 ImportError 的异常。

反之,当你使用如 import item.subitem.subsubitem 这样的语法时,每一个除了最后一个元素都必须是一个包。最后一个元素可以是一个模块或者是要给包,但是不能是一个在前一个元素中定义的类或者函数、变量。

6.4.1 从包中导入 *

如果用户使用 from sound.effects import * 会发生什么呢?在理想情况下,你会希望它通过某种方式出来并进入文件系统,找到哪一个子模块在包中出现,然后将它们全部导入。这也许会花费很长时间,导入子模块可能会有不下想要的副作用,这些副作用只应该在子模块显式地导入的时候出现。

唯一的解决方案是包的作者提供一个显式的包的索引。import 语句使用下面的约定:如果一个包的 __init__.py 代码定义了一个名叫 __all__ 的列表,它会被拿来用作模块的名字列表,这个列表应该在遇到 from package import * 的时候被导入。在包的系版本发布时保持这个列表的更新是由包的作者决定的。如果从他们的包中导入 * 看起没什么用,包的作者也可以决定不支持。举个例子,文件 sound/effects/__init__.py 可以包含一下代码:

  1. __all__ = ["echo", "surround", "reverse"]

这就表示 from sound.effects import * 会从 sound 中导入这三个指名的子模块。

如果 __all__ 没有定义,from sound.effects import * 语句就 不会 把所有的子模块从 sound.effects 包中导入到当前命名空间。它仅仅保证了包 sound.effects 已经被导入 (有可能在 __init__.py 中运行任何一个初始化代码),然后不论什么名字在保重被定义了,都会被导入。它包含了任何已经定义的名字 (和显式地加载的子模块)。它还包含了任何一个通过前一个 import 语句显式地加载的包的子模块。考虑下面的代码:

  1. import sound.effects.echo
  2. import sound.effects.surround
  3. from sound.effects import *

在这个例子中,echosurround 模块在当前的命名空间被导入了,因为当 from...import 语句执行的时候,它们在 sound.effects 包中被定义了。(当 __all__ 定义的之后也可以。)

尽管某些模块设计出来只能在你使用 * 时,导出跟在某些样式后面的名字,它在实际生产代码中仍然被认为很糟糕。

记住了,使用 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 中,你可以使用:

  1. from . import echo
  2. from .. import formats
  3. from ..filters import equalizer

注意,相关的导入以当前模块名为基础。因为主模块名总是 __main__,所以模块作为实用的 Python 应用的主模块,必须总使用绝对导入。

6.4.3 多目录中的包

包还支持一个属性,那就是 __path__。他被初始化为一个列表,在文件中的代码被执行之前,这个列表包含了含有 __init__.py 的目录名。这个变量可以被改变。改变这个变量就会影响将来的包中模块和子包的搜索。

这个特性不常用,它可以用来扩展在一个包中找到的一些列模块。