Python 包(package)
包是一种通过使用“圆点分割模块名称”构造 Python 模块命名空间的方法。例如,模块名 A.B 表示在名为 A 的包中指定了名为 B 的子模块。就像使用模块可以避免不同模块的作者担心彼此的全局变量名一样,使用圆点分割模块名的方式可以避免 NumPy 或 Pillow 等多模块包的作者担心彼此的模块名。
假设你想要设计一个模块集合(“包”),以便统一处理声音文件和声音数据。有许多不同的声音文件格式(通常通过扩展名识别,例如:.wav、.aiff、.au),因此可能需要创建和维护越来越多的模块集合,以便在各种文件格式之间进行转换。你可能还需要对声音数据执行许多不同的操作(例如混音、添加回声、应用均衡器功能、创建人造立体声效果),除此之外,还需要编写一个持续运行的模块流来执行这些操作。下面是包的可能结构(用分层文件系统表示):
sound/ 顶层包
__init__.py 初始化 sound 包
formats/ 用于文件格式转换的子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ 用于音效的子包
__init__.py
echo.py
surround.py
reverse.py
...
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 时,该导入项可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数、类或变量。import 语句首先测试项目是否在包中定义;如果不是,则假定它是一个模块并尝试加载它。如果找不到,则引发 ImportError 异常。
相反,当使用像 import item.subitem.subsubitem 这样的语法时,除最后一项外的每一项都必须是一个包;最后一项可以是模块或包,但不能是前一项中定义的类、函数或变量。
从包导入 *
现在,当用户写出 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 并没有错!事实上,除非导入模块需要使用来自不同包的同名子模块,否则这是推荐的表示法。
包内参考
当包含有子包时(如示例中的 sound 包),可以使用绝对导入来引用同级包的子模块。例如,如果模块 sound.filters.vocoder 需要使用 sound.effects 包中的 echo 模块。可以使用 from sound.effects import echo。
还可以在导入语句中使用 from module import name 编写相对导入。这些导入使用前导圆点指示相对导入中涉及的当前包和父包。例如,在 surround 模块中,可以使用:
from . import echo
from .. import formats
from ..filters import equalizer
复制
请注意,相对导入基于当前模块的名称。由于主模块的名称始终为 "__main__",因此用作 Python 应用程序主模块的模块必须始终使用绝对导入。
多个目录中的包
包还支持一个特殊属性,__path__。这将被初始化为一个列表,其中包含在执行该文件中的代码之前保存包的 __init__.py 文件的目录名称。这个变量可以修改;这样做会影响将来对包中包含的模块和子包的搜索。
虽然此功能通常不需要,但它可以用于扩展包中的模块集。
官方文档:
https://docs.python.org/3.9/tutorial/modules.html