Python文档学习笔记(9)--包
包是一种构建 Python 模块的命名空间的方式,采用“点分模块名称”。例如,模块名称A.B
指定了包A
中名为B
的子模块。
假设你想要设计一系列模块(或一个“包”)来统一处理声音文件和声音数据。有很多不同的声音文件格式 (通常用其扩展名识别,例如︰ .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 ...
__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
这样的语法时,除了最后一项其它每项必须是一个包;最后一项可以是一个模块或一个包,但不能是在前一个项目中定义的类、函数或变量。
从包中导入 *
当用户输入 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 spam import *
的时候会将 模块
中非下划线开头的成员都导入当前命名空间中,这样当然就有可能弄脏当前命名空间。如果显式声明了 __all__
,import *
就只会导入 __all__
列出的成员。如果 __all__
定义有误,列出的成员不存在,还会明确地抛出异常,而不是默默忽略。
包内引用
如果一个包是子包(比如例子中的 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
所在目录的列表。这个变量可以修改;这样做会影响未来包中包含的模块和子包的搜索。
虽然通常不需要此功能,它可以用于扩展包中的模块的集合。