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


141

在Python中,命名空间包可让您在多个项目中传播Python代码。当您要将相关的库作为单独的下载发布时,这很有用。例如,目录Package-1Package-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
在我看来,module1和module2实际上是子包,而不是模块。据我了解,模块基本上是单个文件。也许subpkg1和subpkg2作为名称更有意义?
艾伦

Answers:


79

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在你的sys.pathimport package.fooimport package.bar并且import package.baz将所有的工作。

pkg_resources.declare_namespace(__name__) 尚未更新为包括隐式名称空间包。


2
那setuptools呢?我必须使用该namespace_packages选项吗?那__import__('pkg_resources').declare_namespace(__name__)东西呢?
kawing-chiu

3
我要补充namespace_packages=['package']setup.py
劳伦·拉波特

1
@clacke:使用时namespace_packages=['package'],setup.py将namespace_packages.txt在EGG-INFO中添加一个。仍然不知道会产生什么影响……
Laurent LAPORTE

1
@ kawing-chiu pkg_resources.declare_namespaceover 的好处pkgutil.extend_path是它将继续监视sys.path。这样,如果sys.path在第一次加载名称空间中的程序包后添加了新项目,则仍然可以加载该新路径项目中名称空间中的程序包。(使用__import__('pkg_resources')over 的好处import pkg_resources是您最终pkg_resources不会暴露为my_namespace_pkg.pkg_resources。)
Arthur Tacca

1
@clacke不能那样工作(但效果和它一样)。它维护使用该函数创建的所有程序包名称空间的全局列表,并监视sys.path。当sys.path改变了它检查如果影响__path__任何命名空间的,如果是的话那么它更新这些__path__属性。
亚瑟塔卡

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

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


17
与之相比,使用它的优缺点是什么? import __('pkg_resources')。declare_namespace(__ name)相比有什么优缺点
joeforker

14
首先,__import__在这种情况下,它被认为是不良样式,因为它可以很容易地用纯导入语句替换。更重要的是,pkg_resources是一个非标准库。它带有setuptools,但这不是问题。快速搜索可以发现pkgutil是在2.5中引入的,而pkg_resources早于它。但是,pkgutil是官方认可的解决方案。实际上,在PEP 365中拒绝了pkg_resources包含。
麦克Hordecki

3
来自PEP 382的引文:当前对名称空间包的命令性方法已导致提供名称空间包的多种稍微不兼容的机制。例如,pkgutil支持* .pkg文件;setuptools没有。同样,setuptools支持检查zip文件,并支持在_namespace_packages变量中添加部分内容,而pkgutil不支持。
Drake Guan

7
这两行是否应该放在两个文件中:Package-1/namespace/__init__.py 并且 Package-2/namespace/__init__.py假设我们不知道首先列出哪个Package dir?
布拉2013年

3
@ChristofferKarlsson是的,这很重要,如果您知道哪个是第一个就可以了,但是真正的问题是,您是否可以保证在任何情况下(对于其他用户)它都将是第一个?
布拉


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和迄今似乎是工作了。


-9

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

我说的是诸如zope.x一堆相关软件包作为单独下载发布的事情。
joeforker

好的,但是您要达到的效果是什么。如果包含相关软件包的文件夹全部位于PYTHONPATH上,Python解释器将为您轻松找到它们。
Tendayi Mawushe 09年

5
如果将Package-1和Package-2都添加到PYTHONPATH,Python只会看到Package-1 / namespace /。
索伦Løvborg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.