152

在 Python 中,命名空间包允许您在多个项目之间传播 Python 代码。当您想要将相关库作为单独的下载发布时,这很有用。例如,对于目录Package-1Package-2in PYTHONPATH

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 产品可以在该命名空间中定义模块?

4

5 回答 5

91

TL;博士:

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


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

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

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

包也是模块,但是当我说“模块”时,我的意思是“非包模块”。

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

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

当你这样做import foo.bar时,上面的搜索首先发生 for foo,然后如果找到一个包,搜索将bar作为foo.__path__搜索路径而不是sys.path. 如果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.pathimport package.fooimport package.bar并且import package.baz都可以工作。

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

于 2014-12-21T02:44:44.057 回答
81

有一个名为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了解更多信息)

正如文档所说:

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

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

于 2009-11-04T19:32:17.373 回答
5

这部分应该是不言自明的。

简而言之,把命名空间代码放进去__init__.py,更新setup.py声明一个命名空间,你就可以走了。

于 2009-11-04T19:33:51.873 回答
2

这是一个老问题,但最近有人在我的博客上评论说我关于命名空间包的帖子仍然相关,所以我想我会在这里链接到它,因为它提供了一个如何实现它的实际示例:

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

诀窍是在很大程度上推动了TiddlyWeb__import__("pkg_resources").declare_namespace(__name__)中插件的管理,到目前为止似乎正在奏效。

于 2013-02-05T15:30:52.460 回答
-10

你有你的 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
于 2009-11-04T18:35:20.617 回答