打包命名空间包

命名空间包允许您将子包和模块拆分为一个 package 跨多个,单独 distribution packages (简称 分配 在本文件中避免歧义)。例如,如果您具有以下包结构:

mynamespace/
    __init__.py
    subpackage_a/
        __init__.py
        ...
    subpackage_b/
        __init__.py
        ...
    module_b.py
setup.py

在代码中使用这个包,就像这样:

from mynamespace import subpackage_a
from mynamespace import subpackage_b

然后您可以将这些子包分解为两个单独的分发版:

mynamespace-subpackage-a/
    setup.py
    mynamespace/
        subpackage_a/
            __init__.py

mynamespace-subpackage-b/
    setup.py
    mynamespace/
        subpackage_b/
            __init__.py
        module_b.py

现在可以单独安装、使用和版本控制每个子包。

名称空间包对于大量松散相关的包(例如单个公司的多个产品的大型客户机库库)集合很有用。但是,名称空间包附带了几个注意事项,并非在所有情况下都适用。一个简单的替代方法是在所有分发版上使用前缀,例如 import mynamespace_subpackage_a (你甚至可以用 import mynamespace_subpackage_a as subpackage_a 使导入对象保持简短)。

创建命名空间包

目前有三种不同的方法来创建命名空间包:

  1. 使用 native namespace packages . 此类型的命名空间包在中定义 PEP 420 在python 3.3及更高版本中提供。如果您的命名空间中的包只需要通过 pip .

  2. 使用 pkgutil-style namespace packages . 对于需要支持python 2和3并通过两者进行安装的新软件包,建议这样做。 pippython setup.py install .

  3. 使用 pkg_resources-style namespace packages . 如果您需要与已经使用此方法的包兼容,或者如果您的包需要具有zip安全性,则建议使用此方法。

警告

虽然本机命名空间包和pkgutil风格的命名空间包在很大程度上是兼容的,但是pkg_资源风格的命名空间包与其他方法不兼容。在向同一名称空间提供包的不同发行版中使用不同的方法是不可取的。

本机命名空间包

增加了python 3.3 隐性的 命名空间包来自 PEP 420 . 创建本机名称空间包所需的只是省略 __init__.py 来自命名空间包目录。示例文件结构:

setup.py
mynamespace/
    # No __init__.py here.
    subpackage_a/
        # Sub-packages have __init__.py.
        __init__.py
        module.py

非常重要的是,使用名称空间包的每个分发都忽略了 __init__.py 或者使用pkgutil样式 __init__.py . 如果没有任何分发,它将导致命名空间逻辑失败,其他子包将不可导入。

因为 mynamespace 不包含 __init__.pysetuptools.find_packages() 找不到子包。你必须使用 setuptools.find_namespace_packages() 而是显式地列出 setup.py . 例如:

from setuptools import setup, find_namespace_packages

setup(
    name='mynamespace-subpackage-a',
    ...
    packages=find_namespace_packages(include=['mynamespace.*'])
)

两个本机命名空间包的完整工作示例可以在 native namespace package example project .

注解

由于本机和pkgutil样式的命名空间包在很大程度上是兼容的,因此可以在仅支持python 3的分发中使用本机命名空间包,在需要支持python 2和3的分发中使用pkgutil样式的命名空间包。

pkgutil样式的命名空间包

python 2.3引入了 pkgutil 模块与 extend_path 功能。这可用于声明需要与python 2.3+和python 3兼容的命名空间包。这是实现最高级别兼容性的建议方法。

要创建pkgutil样式的命名空间包,需要提供 __init__.py 命名空间包的文件:

setup.py
mynamespace/
    __init__.py  # Namespace package __init__.py
    subpackage_a/
        __init__.py  # Sub-package __init__.py
        module.py

这个 __init__.py 命名空间包的文件需要包含 only 以下内容:

__path__ = __import__('pkgutil').extend_path(__path__, __name__)

每个 使用命名空间包的分发必须包含相同的 __init__.py . 如果没有任何分发,它将导致命名空间逻辑失败,其他子包将不可导入。中的任何附加代码 __init__.py 将无法访问。

pkgutil namespace example project .

pkg_resources-style命名空间包

Setuptools 提供 pkg_resources.declare_namespace 函数和 namespace_packages 参数 setup() . 这些可以一起用于声明命名空间包。虽然不再推荐这种方法,但它广泛存在于大多数现有的命名空间包中。如果要在使用此方法的现有命名空间包中创建新分发,建议继续使用此方法,因为不同的方法不交叉兼容,不建议尝试迁移现有包。

要创建pkg_resources-style命名空间包,需要提供 __init__.py 命名空间包的文件:

setup.py
mynamespace/
    __init__.py  # Namespace package __init__.py
    subpackage_a/
        __init__.py  # Sub-package __init__.py
        module.py

这个 __init__.py 命名空间包的文件需要包含 only 以下内容:

__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__)

这背后的想法是,在很少的情况下,安装工具不可用的包将回落到pkgutil风格的包。这是不可取的,因为pkgutil和pkg_资源风格的命名空间包不交叉兼容。如果存在SETUPTOOLS是一个问题,那么包应该只显式依赖于SETUPTOOLS VIA install_requires .

最后,每个分发必须提供 namespace_packages 参数 setup() 在里面 setup.py . 例如:

from setuptools import find_packages, setup

setup(
    name='mynamespace-subpackage-a',
    ...
    packages=find_packages()
    namespace_packages=['mynamespace']
)

pkg_resources namespace example project .