Python 1.5 中的内置包支持
Python 1.5 中的内置包支持
从 Python 1.5a4 版本开始,包支持已内置到 Python 解释器中。这实现了“ni”模块首创的包导入语义的稍微简化和修改的版本。
“包导入”是一种通过使用“点式模块名称”来组织 Python 模块命名空间的方法。例如,模块名称 A.B 表示包 A 中名为 B 的子模块。正如使用模块可以避免不同模块的作者担心彼此的全局变量名称一样,使用点式模块名称可以避免 NumPy 或 PIL 等多模块包的作者担心彼此的模块名称。
从 Python 1.3 版本开始,包导入由一个标准的 Python 库模块“ni”支持。(这个名字应该是 New Import 的首字母缩写,但实际上指的是电影《蒙蒂·派森与圣杯》中的《说尼的骑士》,在亚瑟王的骑士带回灌木丛后,他们的名字改成了《说尼奥...呜...平的骑士》- 但那是另一个故事。)
除了对 Python 解析器(也在 1.3 中引入)进行了一些修改以接受“import A.B.C”和“from A.B.C import X”形式的 import 语句之外,ni 模块的所有代码都是用户代码。当 ni 未启用时,使用此语法会导致运行时错误“没有此模块”。一旦启用 ni(在导入其他模块之前执行“import ni”),ni 的导入钩子将查找正确包的子模块。
新的包支持旨在类似于 ni,但已进行了简化,并且一些功能已更改或删除。
一个例子
假设您想要设计一个用于统一处理声音文件和声音数据的包。有许多不同的声音文件格式(通常通过其扩展名识别,例如 .wav、.aiff、.au),因此您可能需要创建和维护一个不断增长的模块集合,用于在各种文件格式之间进行转换。您还可能需要对声音数据执行许多不同的操作(例如,混音、添加回声、应用均衡器功能、创建人工立体声效果),因此,此外,您将编写永无止境的模块流来执行这些操作。以下是您的包的可能结构(以分层文件系统的形式表示)
Sound/ Top-level package __init__.py Initialize the sound package Utils/ Subpackage for internal use __init__.py iobuffer.py errors.py ... 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 dolby.py ...
包的用户可以从包中导入单个模块,例如
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 的语法时,除了最后一个项目之外,每个项目都必须是一个包;最后一个项目可以是模块或包,但不能是前一个项目中定义的类或函数或变量。
从包中导入 *;__all__
属性
现在,当用户编写 from Sound.Effects import *
时会发生什么?理想情况下,人们会希望它以某种方式进入文件系统,查找包中存在的子模块,并全部导入它们。不幸的是,此操作在 Mac 和 Windows 平台上效果不佳,因为文件系统并不总是具有关于文件名的准确大小写信息!在这些平台上,无法保证知道是否应该将文件 ECHO.PY 导入为模块 echo、Echo 或 ECHO。(例如,Windows 95 具有将所有文件名显示为首字母大写的令人讨厌的习惯。)DOS 8+3 文件名限制为长模块名称添加了另一个有趣的问题。
唯一的解决方案是让包作者提供包的显式索引。import 语句使用以下约定:如果包的 __init__.py 代码定义了一个名为 __all__ 的列表,则当遇到 from
package import
* 时,该列表将被视为应导入的模块名称列表。包作者有责任在发布新版本的包时保持此列表的最新状态。如果包作者认为从他们的包中导入 * 没有用处,他们也可以决定不支持它。例如,文件 Sounds/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 语句显式加载的包的任何子模块,例如
在此示例中,echo 和 surround 模块在当前命名空间中导入,因为它们在执行 from...import 语句时在 Sound.Effects 包中定义。(当定义了 __all__ 时,这也可以工作。)import Sound.Effects.echo import Sound.Effects.surround from Sound.Effects import *
请注意,通常不赞成从模块或包中导入 *,因为它通常会导致代码难以阅读。但是,在交互式会话中为了节省键入时间而使用它是可以的,并且某些模块被设计为仅导出遵循某些模式的名称。
请记住,使用 from Package import specific_submodule
没有什么问题!实际上,除非导入模块需要使用来自不同包的同名子模块,否则这成为推荐的表示法。
包内引用
子模块通常需要互相引用。例如,surround 模块可能会使用 echo 模块。实际上,这种引用非常常见,以至于 import 语句首先在包含包中查找,然后再在标准模块搜索路径中查找。因此,surround 模块可以简单地使用 import echo
或 from echo import echofilter
。如果在当前包(当前模块是其子模块的包)中找不到导入的模块,则 import 语句会查找具有给定名称的顶级模块。
当包被组织成子包时(如示例中的 Sound 包),没有快捷方式来引用兄弟包的子模块 - 必须使用子包的全名。例如,如果模块 Sound.Filters.vocoder 需要使用 Sound.Effects 包中的 echo 模块,则可以使用 from Sound.Effects import echo
。
(人们可以设计一种表示法来引用父包,类似于在 Unix 和 Windows 文件系统中使用“..”来引用父目录。实际上,ni 使用 __ 表示包含当前模块的包,使用 __.__ 表示父包,依此类推。由于它的笨拙,此功能被删除了;由于大多数包都具有相对较浅的子结构,因此这并不是什么大损失。)
详情
包也是模块!
警告:对于那些熟悉 Java 包表示法的人来说,以下内容可能会令人困惑,它与 Python 的表示法相似,但又不同。
每当加载包的子模块时,Python 都会确保首先加载包本身,并在必要时加载其 __init__.py 文件。包也是如此。因此,当执行语句 import Sound.Effects.echo
时,它首先确保加载 Sound;然后它确保加载 Sound.Effects;只有这样,它才确保加载 Sound.Effects.echo(如果之前未加载则加载)。
一旦加载,包和模块之间的差异就非常小。实际上,两者都由模块对象表示,并且都存储在加载模块的表 sys.modules 中。sys.modules 中的键是模块的完整点式名称(不总是与 import 语句中使用的名称相同)。这也是 __name__ 变量的内容(它提供模块或包的完整名称)。
__path__ 变量
包和模块之间的一个区别在于变量 __path__ 的存在与否。这仅适用于包。它初始化为一个包含一个项目的列表,其中包含包的目录名称(sys.path 上某个目录的子目录)。更改 __path__ 会更改搜索包子模块的目录列表。例如,Sound.Effects 包可能包含特定于平台的子模块。它可以使用以下目录结构
Sound/ __init__.py Effects/ # Generic versions of effects modules __init__.py echo.py surround.py reverse.py ... plat-ix86/ # Intel x86 specific effects modules echo.py surround.py plat-PPC/ # PPC specific effects modules echo.py
Effects/__init__.py 文件可以操作其 __path__ 变量,以便适当的平台特定子目录位于主 Effects 目录之前,以便某些效果的平台特定实现(如果可用)覆盖通用(可能较慢)实现。例如
platform = ... # Figure out which platform applies dirname = __path__[0] # Package's main folder __path__.insert(0, os.path.join(dirname, "plat-" + platform))
如果不需要特定于平台的子模块隐藏具有相同名称的通用模块,则应使用 __path__.append(...) 而不是 __path__.insert(0, ...)。
请注意,plat-* 子目录不是 Effects 的子包 - 文件 Sound/Effects/plat-PPC/echo.py 对应于模块 Sound.Effects.echo。
sys.modules
中的虚拟条目
使用包时,您可能会偶尔在 sys.modules 中发现虚假的条目,例如,sys.modules['Sound.Effects.string'] 的值可能为 None。这是一个“间接”条目,它的创建是因为 Sound.Effects 包中的某个子模块导入了顶层的 string 模块。它的目的是一个重要的优化:因为 import 语句无法判断是需要本地模块还是全局模块,并且规则规定本地模块(在同一个包中)会隐藏具有相同名称的全局模块,所以 import 语句必须在查找(可能已经导入的)全局模块之前搜索包的搜索路径。由于搜索包的路径是一项相对昂贵的操作,而导入一个已经导入的模块应该很便宜(大约一两次字典查找),所以进行优化是必要的。当同一个全局模块被同一个包的子模块第二次导入时,这个虚拟条目可以避免搜索包的路径。
虚拟条目只为在顶层找到的模块创建;如果根本找不到该模块,则导入会失败,并且通常不需要优化。此外,在交互式使用中,用户可以将该模块创建为包本地子模块并重试导入;如果创建了虚拟条目,则将无法找到它。如果用户通过创建与包中已使用的全局模块同名的本地子模块来更改包结构,则结果通常被称为“一团糟”,正确的解决方案是退出解释器并重新启动。
如果我有一个同名的模块和一个包怎么办?
您可能有一个目录(在 sys.path 上),其中既有模块 spam.py,又有一个包含 __init__.py 的子目录 spam(如果没有 __init__.py,则该目录不会被识别为包)。在这种情况下,子目录具有优先权,导入 spam 将忽略 spam.py 文件,而加载包 spam。如果您希望模块 spam.py 具有优先权,则必须将其放置在 sys.path 中较早出现的目录中。
(提示:搜索顺序由函数 imp.get_suffixes() 返回的后缀列表决定。通常,后缀的搜索顺序如下:".so"、"module.so"、".py"、".pyc"。目录不会明确出现在此列表中,但它会优先于其中的所有条目。)
软件包安装建议
为了使 Python 程序可以使用包,该包必须能够被 import 语句找到。换句话说,该包必须是 sys.path 上目录的子目录。
传统上,确保包在 sys.path 上的最简单方法是将其安装在标准库中,或让用户通过设置其 $PYTHONPATH shell 环境变量来扩展 sys.path。实际上,这两种解决方案都很快导致混乱。
专用目录
在 Python 1.5 中,建立了一个应该可以防止混乱的约定,让系统管理员拥有更多的控制权。首先,在默认搜索路径的末尾添加了两个额外的目录(如果安装前缀和执行前缀不同,则为四个)。这些目录相对于安装前缀(默认为 /usr/local):
- $prefix/lib/python1.5/site-packages
- $prefix/lib/site-python
site-packages 目录可用于可能依赖于 Python 版本的包(例如,包含共享库或使用新功能的包)。site-python 目录用于向后兼容 Python 1.4,以及不敏感于所使用的 Python 版本的纯 Python 包或模块。
建议使用这些目录是将每个包放置在 site-packages 或 site-python 目录中其自己的子目录中。子目录应该是包名,该包名应该可以接受为 Python 标识符。然后,任何 Python 程序都可以通过给出模块的全名来导入包中的模块。例如,示例中使用的 Sound 包可以安装在 $prefix/lib/python1.5/site-packages/Sound 目录中,以启用诸如 import Sound.Effects.echo
之类的导入语句。
添加一个间接层
一些站点希望将其软件包安装在其他位置,但仍然希望所有用户运行的所有 Python 程序都可以导入它们。这可以通过两种不同的方式来实现:
- 符号链接
- 如果包的结构用于点式名称导入,请在 site-packages 或 site-python 目录中放置指向其顶层目录的符号链接。符号链接的名称应为包名称;例如,Sound 包可能具有一个符号链接 $prefix/lib/python1.5/site-packages/Sound,指向 /usr/home/soundguru/lib/Sound-1.1/src。
- 路径配置文件
- 如果包确实需要将一个或多个目录添加到 sys.path 上(例如,因为它尚未构建为支持点式名称导入),则可以在 site-python 或 site-packages 目录中放置一个名为 package.pth 的“路径配置文件”。此文件中的每一行(注释和空行除外)都被认为包含一个目录名称,该目录名称将附加到 sys.path。允许使用相对路径名,并将其解释为相对于包含 .pth 文件的目录。
.pth 文件按字母顺序读取,大小写敏感性与本地文件系统相同。这意味着,如果您发现自己有不可抗拒的冲动想要玩弄目录搜索顺序的游戏,至少可以以可预测的方式进行。(这与认可不同。典型的安装应该没有或很少有 .pth 文件,否则就有问题,如果您需要玩弄搜索顺序,那就非常有问题了。尽管如此,有时确实会产生这种需求,这就是您必须这样做的方式。)
Mac 和 Windows 平台注意事项
在 Mac 和 Windows 上,约定略有不同。在这些平台上,软件包安装的传统目录是 Python 安装目录的根目录(或子目录),该目录特定于已安装的 Python 版本。这也是搜索路径配置文件 (*.pth) 的(唯一)目录。
标准库目录的子目录
由于 sys.path 上目录的任何子目录现在都可以隐式用作包,因此人们很容易对这些目录是否打算这样做感到困惑。例如,假设有一个名为 tkinter 的子目录,其中包含一个模块 Tkinter.py。应该编写 import Tkinter 还是 import tkinter.Tkinter?如果 tkinter 子目录在路径上,两者都可以工作,但这会造成不必要的混淆。
我建立了一个简单的命名约定,应该可以消除这种混乱:非包目录的名称中必须带有连字符。特别是,所有平台特定的子目录(sunos5、win、mac 等)都已重命名为带有前缀“plat-”的名称。尚未转换为包的可选 Python 组件的特定子目录已重命名为带有前缀“lib-”的名称。dos_8x3 子目录已重命名为 dos-8x3。下表列出了所有重命名的目录:
旧名称 | 新名称 |
tkinter | lib-tk |
stdwin | lib-stdwin |
sharedmodules | lib-dynload |
dos_8x3 | dos-8x3 |
aix3 | plat-aix3 |
aix4 | plat-aix4 |
freebsd2 | plat-freebsd2 |
generic | plat-generic |
irix5 | plat-irix5 |
irix6 | plat-irix6 |
linux1 | plat-linux1 |
linux2 | plat-linux2 |
next3 | plat-next3 |
sunos4 | plat-sunos4 |
sunos5 | plat-sunos5 |
win | plat-win |
test | test |
请注意,test 子目录不会重命名。它现在是一个包。要调用它,请使用类似 import test.autotest
的语句。
其他内容
XXX 我还没有时间写出以下项目的讨论:与 ni
的更改
以下 ni 的功能尚未完全复制。除非您当前正在使用 ni 模块并且希望迁移到内置的包支持,否则请忽略本节。
已删除 __domain__
默认情况下,当包 A.B.C 的子模块导入模块 X 时,ni 将按 A.B.C.X、A.B.X、A.X 和 X 的顺序搜索。这是由包中的 __domain__ 变量定义的,该变量可以设置为要搜索的包名称列表。此功能在内置的包支持中已删除。相反,搜索总是先查找 A.B.C.X,然后再查找 X。(这是对 Python 中其他地方成功用于名称空间解析的“双作用域”方法的反转。)
已删除 __
使用 ni,包可以使用特殊名称“__”(两个下划线)来使用显式的“相对”模块名称。例如,包 A.B.C 中的模块可以通过 __.__.K.module 形式的名称来引用在包 A.B.K 中定义的模块。此功能因其有限的用途和较差的可读性而被删除。
__init__
的不兼容语义
使用 ni,包内的 __init__.py 文件(如果存在)将被导入为包的标准子模块。而内置的包支持会将 __init__.py 文件加载到包的命名空间中。这意味着,如果包 A 中的 __init__.py 定义了名称 x,则可以将其称为 A.x 而无需其他操作。使用 ni,__init__.py 必须包含 __.x = x
形式的赋值才能获得相同的效果。
此外,新的包支持要求存在一个 __init__
模块;在 ni 下,它是可选的。这是在 Python 1.5b1 中引入的更改;它旨在避免具有通用名称(如“string”)的目录无意中隐藏稍后出现在模块搜索路径上的有效模块。
希望与 ni 向后兼容的包可以测试特殊变量 __ 是否存在,例如:
# Define a function to be visible at the package level def f(...): ... try: __ except NameError: # new built-in package support pass else: # backwards compatibility for ni __.f = f