如何在Python中创建命名空间包?
在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产品可以在这个命名空间中定义模块呢?
5 个回答
简单来说,把命名空间的代码放在 __init__.py
文件里,然后更新 setup.py
文件来声明一个命名空间,这样你就可以开始了。
有一个标准模块,叫做 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)
根据 文档 的说明:
这会把所有在
sys.path
中与这个包同名的目录的子目录添加到包的__path__
中。
从现在开始,你应该可以独立地分发这两个包了。
简而言之:
在Python 3.3中,你不需要做任何事情,只要在你的命名空间包目录中不放任何__init__.py
文件,它就会正常工作。在3.3之前的版本中,建议使用pkgutil.extend_path()
的方法,而不是pkg_resources.declare_namespace()
,因为前者更具未来兼容性,并且已经支持隐式命名空间包。
Python 3.3引入了隐式命名空间包,具体内容可以查看PEP 420。
这意味着现在通过import foo
可以创建三种类型的对象:
- 一个由
foo.py
文件表示的模块 - 一个由包含
__init__.py
文件的目录foo
表示的常规包 - 一个由没有任何
__init__.py
文件的一个或多个目录foo
表示的命名空间包
包也是模块,但这里我说“模块”是指“非包模块”。
首先,它会扫描sys.path
来查找模块或常规包。如果找到,就停止搜索并创建和初始化该模块或包。如果没有找到模块或常规包,但找到了至少一个目录,它就会创建和初始化一个命名空间包。
模块和常规包的__file__
属性会设置为它们创建时的.py
文件。常规包和命名空间包的__path__
属性会设置为它们创建时的目录。
当你执行import foo.bar
时,首先会对foo
进行上述搜索,然后如果找到了包,接着会用foo.__path__
作为搜索路径来查找bar
,而不是用sys.path
。如果找到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
和import package.baz
都会正常工作。
pkg_resources.declare_namespace(__name__)
并没有更新以包含隐式命名空间包。