如何在 Python 中创建命名空间包?
- 2025-01-22 08:45:00
- admin 原创
- 75
问题描述:
在 Python 中,命名空间包允许您将 Python 代码分散到多个项目中。当您想将相关库作为单独的下载发布时,这很有用。例如,使用中的目录Package-1
和,Package-2
`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.module1
并且import namespace.module2
。
定义命名空间包的最佳方法是什么,以便多个 Python 产品可以在该命名空间中定义模块?
解决方案 1:
总结:
在 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.barfoo
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
和import package.baz
都可以。
pkg_resources.declare_namespace(__name__)
尚未更新以包含隐式命名空间包。
解决方案 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 )
正如文档所述:
这会将以该包命名的
__path__
目录的所有子目录添加到包中sys.path
。
从现在开始,您应该能够独立分发这两个包。
解决方案 3:
本节内容应非常清晰易懂。
简而言之,将命名空间代码放入__init__.py
,更新setup.py
以声明命名空间,然后就可以自由使用了。
解决方案 4:
这是一个老问题,但最近有人在我的博客上评论说我关于命名空间包的帖子仍然具有相关性,所以我想在这里链接到它,因为它提供了一个如何实现它的实用示例:
这篇文章的链接可以让你了解事情的主要内容:
http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package
这个__import__("pkg_resources").declare_namespace(__name__)
技巧基本上推动了TiddlyWeb中插件的管理,到目前为止似乎很有效。
解决方案 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