如何在 Python 中创建名称空间包?

在 Python 中,命名空间包允许您将 Python 代码分散到多个项目中。当您希望将相关库作为单独的下载发布时,这非常有用。例如,对于 PYTHONPATH中的目录 Package-1Package-2,

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

最终用户可以 import namespace.module1import namespace.module2

定义一个名称空间包的最佳方法是什么,以便不止一个 Python 产品可以在该名称空间中定义模块?

77108 次浏览

您将 Python 命名空间概念放在前面,在 Python 中不可能将包放到模块中。包包含模块,而不是反过来。

Python 包只是一个包含 __init__.py文件的文件夹。模块是包中(或直接在 PYTHONPATH上)具有 .py扩展名的任何其他文件。因此在您的示例中有两个包,但是没有定义模块。如果您认为一个包是一个文件系统文件夹,而一个模块是文件,那么您就会明白为什么包包含模块,而不是反过来。

因此,在您的示例中,假设 Package-1和 Package-2是文件系统上的文件夹,并且您已经将它们放在 Python 路径上,那么您可以拥有以下内容:

Package-1/
namespace/
__init__.py
module1.py
Package-2/
namespace/
__init__.py
module2.py

现在已经有了一个包 namespace,其中包含两个模块 module1module2。除非你有一个很好的理由,否则你应该把模块放在文件夹中,并且只在 python 路径中放这些模块,如下所示:

Package-1/
namespace/
__init__.py
module1.py
module2.py

有一个标准模块,叫 Pkgutil 可以将模块“附加”到给定的命名空间。

使用您提供的目录结构:

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

你应该在 Package-1/namespace/__init__.pyPackage-2/namespace/__init__.py(*)中都写上这两句话:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

(* 因为-除非你声明它们之间的依赖关系-你不知道它们中的哪一个会首先被识别-请参阅 PEP 420了解更多信息)

正如 文件所言:

这将把 sys.path上以包命名的所有目录的子目录添加到包的 __path__中。

从现在开始,您应该能够独立地分发这两个包。

这一部分应该是不言而喻的。

简而言之,将名称空间代码放在 __init__.py中,更新 setup.py以声明名称空间,然后就可以自由使用了。

这是一个老问题了,但是最近有人在我的博客上评论说我发布的关于名称空间包的文章仍然有意义,所以我想我应该在这里链接到它,因为它提供了一个如何实现它的实际例子:

Https://web.archive.org/web/20150425043954/http://cdent.tumblr.com/post/216241761/python-namespace-packages-for-tiddlyweb

这个链接到这篇文章的主要内容:

Http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package

__import__("pkg_resources").declare_namespace(__name__)的技巧是几乎驱动器的管理插件在 TiddlyWeb和迄今为止似乎是工作。

译者:

在 Python 3.3上,您不需要做任何事情,只需不要将任何 __init__.py放在您的名称空间包目录中,它就会正常工作。在 pre-3.3中,选择 pkgutil.extend_path()解决方案而不是 pkg_resources.declare_namespace()解决方案,因为 pkgutil.extend_path()解决方案是未来的,并且已经与隐式名称空间包兼容。


Python 3.3引入了隐式命名空间包,请参见 PEP 420

这意味着现在有三种类型的对象可以由 import foo创建:

  • foo.py文件表示的模块
  • 一个常规包,由包含 __init__.py文件的目录 foo表示
  • 命名空间包,由一个或多个目录 foo表示,不包含任何 __init__.py文件

包也是模块,但这里我说的“模块”是指“非包模块”。

首先,它扫描 sys.path寻找一个模块或常规包。如果成功,则停止搜索并创建并初始化模块或包。如果它没有找到模块或常规包,但至少找到了一个目录,那么它将创建并初始化一个名称空间包。

模块和常规包将 __file__设置为从中创建的 .py文件。常规包和命名空间包将 __path__set 设置为创建它们的目录。

当执行 import foo.bar时,上面的搜索首先针对 foo进行,然后如果找到一个包,则使用 foo.__path__而不是 sys.path作为搜索路径完成对 bar的搜索。如果找到 foo.bar,则创建并初始化 foofoo.bar

那么,常规包和名称空间包是如何混合的呢?通常它们不会,但是旧的 pkgutil显式名称空间包方法已经扩展为包括隐式名称空间包。

如果您有一个现有的常规软件包,其 __init__.py是这样的:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

... 遗留的行为是将搜索路径上的任何其他 普通的包添加到其 __path__。但是在 Python 3.3中,它还添加了名称空间包。

因此,您可以拥有以下目录结构:

├── path1
│   └── package
│       ├── __init__.py
│       └── foo.py
├── path2
│   └── package
│       └── bar.py
└── path3
└── package
├── __init__.py
└── baz.py

... 只要两个 __init__.pyextend_path线(和 path1path2path3在你的 sys.path) import package.fooimport package.barimport package.baz都将工作。

没有更新 pkg_resources.declare_namespace(__name__)以包含隐式命名空间包。