如何在 Python 中创建命名空间包?
Posted
技术标签:
【中文标题】如何在 Python 中创建命名空间包?【英文标题】:How do I create a namespace package in Python? 【发布时间】:2010-12-13 03:24:11 【问题描述】:在 Python 中,命名空间包允许您在多个项目之间传播 Python 代码。当您想要将相关库作为单独的下载发布时,这很有用。例如,对于PYTHONPATH
中的目录Package-1
和Package-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.module1
和import namespace.module2
。
定义一个命名空间包的最佳方式是什么,以便多个 Python 产品可以在该命名空间中定义模块?
【问题讨论】:
在我看来 module1 和 module2 实际上是子包而不是模块。据我了解,模块基本上是一个文件。也许 subpkg1 和 subpkg2 作为名称更有意义? 【参考方案1】:TL;DR:
在 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__
set 设置为创建它们的目录。
当您执行import foo.bar
时,上述搜索首先针对foo
,然后如果找到一个包,则以foo.__path__
作为搜索路径而不是sys.path
来搜索bar
。如果找到foo.bar
,则创建并初始化foo
和foo.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__.py
有extend_path
行(并且path1
、path2
和path3
在你的sys.path
中)import package.foo
、import package.bar
和@ 987654357@ 都可以。
pkg_resources.declare_namespace(__name__)
尚未更新为包含隐式命名空间包。
【讨论】:
setuptools 怎么样?我必须使用namespace_packages
选项吗?还有__import__('pkg_resources').declare_namespace(__name__)
的事情?
我应该在setup.py
中添加namespace_packages=['package']
吗?
@clacke:使用namespace_packages=['package']
,setup.py 将在EGG-INFO 中添加namespace_packages.txt
。仍然不知道影响......
@kawing-chiu pkg_resources.declare_namespace
优于 pkgutil.extend_path
的好处是它将继续监视 sys.path
。这样,如果在首次加载命名空间中的包之后将新项目添加到sys.path
,则仍然能够加载该新路径项目中的命名空间中的包。 (使用__import__('pkg_resources')
而不是import pkg_resources
的一个好处是您最终不会将pkg_resources
暴露为my_namespace_pkg.pkg_resources
。)
@clacke 它不是那样工作的(但它的效果和它一样)。它维护使用该函数创建的所有包命名空间的全局列表,并监视sys.path
。当sys.path
更改时,它会检查这是否会影响任何命名空间的__path__
,如果是,则更新那些__path__
属性。【参考方案2】:
有一个名为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__.py
和Package-2/namespace/__init__.py
(*) 中:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
(* 因为 - 除非你声明它们之间的依赖关系 - 你不知道它们中的哪一个会首先被识别 - 请参阅 PEP 420 了解更多信息)
正如documentation 所说:
这将添加到包的
__path__
上以包命名的sys.path
目录的所有子目录。
从现在开始,你应该可以独立分发这两个包了。
【讨论】:
与 import__('pkg_resources').declare_namespace(__name) 相比,使用它的优缺点是什么? 首先,__import__
在这种情况下被认为是不好的风格,因为它可以很容易地用简单的 import 语句替换。更重要的是,pkg_resources 是一个非标准库。它带有 setuptools,所以这不是问题。快速谷歌搜索显示 pkgutil 是在 2.5 中引入的,而 pkg_resources 早于它。尽管如此,pkgutil 是一个官方认可的解决方案。事实上,pkg_resources 包含在 PEP 365 中被拒绝。
来自PEP 382 的引用:当前命名空间包的命令式方法导致提供命名空间包的多个稍微不兼容的机制。例如,pkgutil 支持 *.pkg 文件; setuptools 没有。同样,setuptools 支持检查 zip 文件,并支持向其 _namespace_packages 变量添加部分,而 pkgutil 不支持。
是否应该将这两行放入两个文件中:Package-1/namespace/__init__.py
和 Package-2/namespace/__init__.py
,前提是我们不知道首先列出哪个包目录?
@ChristofferKarlsson 是的,这就是重点,如果您知道哪个是第一个就可以了,但真正的问题是您能否保证它在任何情况下都会是第一个,即对于其他用户? 【参考方案3】:
This section should be pretty self-explanatory.
总之,把命名空间代码放在__init__.py
,更新setup.py
声明命名空间,就可以了。
【讨论】:
您应该始终引用链接的相关部分,以防相关链接失效。【参考方案4】:这是一个老问题,但最近有人在我的博客上评论说我关于命名空间包的帖子仍然相关,所以我想我会在这里链接到它,因为它提供了一个如何实现它的实际示例:
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 中插件的管理,到目前为止似乎正在奏效。
【讨论】:
siafoo 链接已损坏,这是存档副本的链接:web.archive.org/web/20200926015931/http://www.siafoo.net/…【参考方案5】:你的 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
,其中包含两个模块module1
和module2
。除非你有充分的理由,否则你可能应该将模块放在文件夹中,并且只在 python 路径中使用,如下所示:
Package-1/
namespace/
__init__.py
module1.py
module2.py
【讨论】:
我说的是zope.x
之类的东西,其中一堆相关的软件包作为单独的下载发布。
好的,但是你想要达到的效果是什么。如果包含相关包的文件夹都在 PYTHONPATH 上,Python 解释器会为您找到它们,而无需您付出额外的努力。
如果您将 Package-1 和 Package-2 同时添加到 PYTHONPATH,Python 只会看到 Package-1/namespace/。以上是关于如何在 Python 中创建命名空间包?的主要内容,如果未能解决你的问题,请参考以下文章
ReactiveMongoDatabase:如何预先创建集合:无法在多文档事务中创建命名空间