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

时间:2009-11-04 18:27:10

标签: python namespaces package

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

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

5 个答案:

答案 0 :(得分:77)

有一个名为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以获取更多信息)

正如documentation所说:

  

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

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

答案 1 :(得分:57)

TL; DR:

在Python 3.3上,您不需要做任何事情,只是不要在命名空间包目录中放置任何__init__.py,它就可以正常工作。在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时,上述搜索会首先针对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在{{1} {},} sys.pathimport package.fooimport package.bar都可以。

import package.baz尚未更新为包含隐式命名空间包。

答案 2 :(得分:5)

This section should be pretty self-explanatory.

简而言之,将命名空间代码放在__init__.py中,更新setup.py以声明命名空间,然后您可以自由选择。

答案 3 :(得分: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

__import__("pkg_resources").declare_namespace(__name__)技巧几乎推动了TiddlyWeb中插件的管理,到目前为止似乎已经有了。

答案 4 :(得分:-8)

您将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