打包命名空间包#
命名空间包允许你将单个 包 中的子包和模块拆分成多个单独的 分发包(本文档中称为分发,以避免歧义)。例如,如果你有以下包结构
mynamespace/
__init__.py
subpackage_a/
__init__.py
...
subpackage_b/
__init__.py
...
module_b.py
pyproject.toml
并且你在代码中像这样使用此包
from mynamespace import subpackage_a
from mynamespace import subpackage_b
那么你可以将这些子包拆分成两个单独的分发
mynamespace-subpackage-a/
pyproject.toml
src/
mynamespace/
subpackage_a/
__init__.py
mynamespace-subpackage-b/
pyproject.toml
src/
mynamespace/
subpackage_b/
__init__.py
module_b.py
现在,每个子包都可以单独安装、使用和进行版本控制。
命名空间包对于大量松散相关的包(例如,来自单一公司的多个产品的庞大客户端库语料库)很有用。但是,命名空间包有一些警告,并不适用于所有情况。一个简单的替代方法是在你的所有分发上使用前缀,例如 import mynamespace_subpackage_a
(你甚至可以使用 import mynamespace_subpackage_a as subpackage_a
来保持导入对象的简短)。
创建命名空间包#
目前有两种不同的创建命名空间包的方法,其中后者不推荐使用
使用 本机命名空间包。这种类型的命名空间包在 PEP 420 中定义,可在 Python 3.3 及更高版本中使用。如果你命名空间中的包只需要支持 Python 3 和通过
pip
安装,则建议使用此方法。使用 旧版命名空间包。这包括 pkgutil 样式命名空间包 和 pkg_resources 样式命名空间包。
本机命名空间包#
Python 3.3 从 PEP 420 添加了隐式命名空间包。创建本机命名空间包所需的所有操作就是从命名空间包目录中省略 __init__.py
。一个示例文件结构(遵循 src-layout)
mynamespace-subpackage-a/
pyproject.toml # AND/OR setup.py, setup.cfg
src/
mynamespace/ # namespace package
# No __init__.py here.
subpackage_a/
# Regular import packages have an __init__.py.
__init__.py
module.py
非常重要的是,使用命名空间包的每个分发都省略 __init__.py
或使用 pkgutil 样式的 __init__.py
。如果任何分发没有这样做,它将导致命名空间逻辑失败,并且其他子包将不可导入。
src-layout
目录结构允许大多数 构建后端 自动发现包。有关更多信息,请参阅 src 布局与平面布局。但是,如果你想自己管理包的排除或包含,可以在顶级 pyproject.toml
中配置此项
[build-system]
...
[tool.setuptools.packages.find]
where = ["src/"]
include = ["mynamespace.subpackage_a"]
[project]
name = "mynamespace-subpackage-a"
...
可以使用 setup.cfg
完成相同操作
[options]
package_dir =
=src
packages = find_namespace:
[options.packages.find]
where = src
或 setup.py
from setuptools import setup, find_namespace_packages
setup(
name='mynamespace-subpackage-a',
...
packages=find_namespace_packages(where='src/', include=['mynamespace.subpackage_a']),
package_dir={'': 'src'},
)
Setuptools 默认情况下将搜索目录结构以查找隐式命名空间包。
可以在 本机命名空间包示例项目 中找到两个本机命名空间包的完整工作示例。
注意
由于原生和 pkgutil 风格的命名空间包在很大程度上兼容,因此您可以在仅支持 Python 3 的发行版中使用原生命名空间包,在需要支持 Python 2 和 3 的发行版中使用 pkgutil 风格的命名空间包。
旧版命名空间包#
在 PEP 420 之前用于创建命名空间包的这两种方法现已视为过时,除非您需要与已使用此方法的包兼容,否则不应使用。此外,pkg_resources 已弃用。
要迁移现有包,必须同时迁移共享命名空间的所有包。
警告
虽然原生命名空间包和 pkgutil 风格的命名空间包在很大程度上兼容,但 pkg_resources 风格的命名空间包与其他方法不兼容。不建议在为同一命名空间提供包的不同发行版中使用不同的方法。
pkgutil 风格的命名空间包#
Python 2.3 引入了 pkgutil 模块和 pkgutil.extend_path()
函数。这可用于声明需要同时兼容 Python 2.3+ 和 Python 3 的命名空间包。这是获得最高兼容性的推荐方法。
要创建 pkgutil 风格的命名空间包,您需要为命名空间包提供 __init__.py
文件
mynamespace-subpackage-a/
src/
pyproject.toml # AND/OR setup.cfg, setup.py
mynamespace/
__init__.py # Namespace package __init__.py
subpackage_a/
__init__.py # Regular package __init__.py
module.py
命名空间包的 __init__.py
文件需要包含以下内容
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
每个使用命名空间包的发行版都必须包含这样的 __init__.py
。如果任何发行版不包含,它将导致命名空间逻辑失败,并且其他子包将无法导入。__init__.py
中的任何其他代码都将无法访问。
可以在 pkgutil 命名空间示例项目 中找到两个 pkgutil 风格的命名空间包的完整工作示例。
pkg_resources 风格的命名空间包#
Setuptools 提供了 pkg_resources.declare_namespace 函数和 namespace_packages
参数,用于 setup()
。这些可以一起用于声明命名空间包。虽然此方法不再推荐,但它广泛存在于大多数现有命名空间包中。如果您正在使用此方法在现有命名空间包中创建新发行版,则建议继续使用此方法,因为不同的方法不具有交叉兼容性,并且不建议尝试迁移现有包。
要创建 pkg_resources 风格的命名空间包,您需要为命名空间包提供 __init__.py
文件
mynamespace-subpackage-a/
src/
pyproject.toml # AND/OR setup.cfg, setup.py
mynamespace/
__init__.py # Namespace package __init__.py
subpackage_a/
__init__.py # Regular package __init__.py
module.py
命名空间包的 __init__.py
文件需要包含以下内容
__import__('pkg_resources').declare_namespace(__name__)
每个使用命名空间包的发行版都必须包含这样的 __init__.py
。如果任何发行版不包含,它将导致命名空间逻辑失败,并且其他子包将无法导入。__init__.py
中的任何其他代码都将无法访问。
注意
一些较旧的建议在命名空间包 __init__.py
中建议以下内容
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
这样做背后的想法是,在极少数情况下 setuptools 不可用的情况下,包会回退到 pkgutil 风格的包。这是不建议的,因为 pkgutil 和 pkg_resources 风格的命名空间包不具有交叉兼容性。如果 setuptools 的存在是一个问题,那么包应该通过 install_requires
显式依赖于 setuptools。
最后,每个发行版都必须在 setup.py
中为 setup()
提供 namespace_packages
参数。例如
from setuptools import find_packages, setup
setup(
name='mynamespace-subpackage-a',
...
packages=find_packages()
namespace_packages=['mynamespace']
)
可以在 pkg_resources 命名空间示例项目 中找到两个 pkg_resources 风格的命名空间包的完整工作示例。