如何在 Python 中创建命名空间包?
问题描述
在 Python 中,命名空间包允许您在多个项目之间传播 Python 代码.当您想要将相关库作为单独的下载发布时,这很有用.例如,使用 PYTHONPATH
中的目录 Package-1
和 Package-2
,
In Python, a namespace package allows you to spread Python code among several projects. This is useful when you want to release related libraries as separate downloads. For example, with the directories Package-1
and Package-2
in 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 产品可以在该命名空间中定义模块?
What's the best way to define a namespace package so more than one Python product can define modules in that namespace?
解决方案
TL;DR:
在 Python 3.3 上,您无需执行任何操作,只需不要将任何 __init__.py
放入您的命名空间包目录中,它就会正常工作.在 3.3 之前,选择 pkgutil.extend_path()
解决方案而不是 pkg_resources.declare_namespace()
解决方案,因为它是面向未来的并且已经与隐式命名空间包兼容.
On Python 3.3 you don't have to do anything, just don't put any __init__.py
in your namespace package directories and it will just work. On pre-3.3, choose the pkgutil.extend_path()
solution over the pkg_resources.declare_namespace()
one, because it's future-proof and already compatible with implicit namespace packages.
Python 3.3 引入了隐式命名空间包,请参阅 PEP 420.
Python 3.3 introduces implicit namespace packages, see PEP 420.
这意味着现在可以通过 import foo
创建三种类型的对象:
This means there are now three types of object that can be created by an import foo
:
- 由
foo.py
文件表示的模块 - 一个常规包,由包含
__init__.py
文件的目录foo
表示 - 一个命名空间包,由一个或多个目录
foo
表示,没有任何__init__.py
文件
- A module represented by a
foo.py
file - A regular package, represented by a directory
foo
containing an__init__.py
file - A namespace package, represented by one or more directories
foo
without any__init__.py
files
包也是模块,但这里我说的模块"是指非包模块".
Packages are modules too, but here I mean "non-package module" when I say "module".
首先它会扫描 sys.path
以查找模块或常规包.如果成功,它将停止搜索并创建和初始化模块或包.如果它没有找到模块或常规包,但它至少找到一个目录,它会创建并初始化一个命名空间包.
First it scans sys.path
for a module or regular package. If it succeeds, it stops searching and creates and initalizes the module or package. If it found no module or regular package, but it found at least one directory, it creates and initializes a namespace package.
模块和常规包将 __file__
设置为创建它们的 .py
文件.常规和命名空间包的 __path__
设置为创建它们的目录.
Modules and regular packages have __file__
set to the .py
file they were created from. Regular and namespace packages have __path__
set to the directory or directories they were created from.
当你执行 import foo.bar
时,上面的搜索首先发生在 foo
,然后如果找到一个包,则搜索 bar
使用 foo.__path__
作为搜索路径而不是 sys.path
.如果找到 foo.bar
,则创建并初始化 foo
和 foo.bar
.
When you do import foo.bar
, the above search happens first for foo
, then if a package was found, the search for bar
is done with foo.__path__
as the search path instead of sys.path
. If foo.bar
is found, foo
and foo.bar
are created and initialized.
那么常规包和命名空间包是如何混合的呢?通常它们不会,但旧的 pkgutil
显式命名空间包方法已扩展为包含隐式命名空间包.
So how do regular packages and namespace packages mix? Normally they don't, but the old pkgutil
explicit namespace package method has been extended to include implicit namespace packages.
如果您有一个现有的常规包,其中包含这样的 __init__.py
:
If you have an existing regular package that has an __init__.py
like this:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
...遗留行为是将搜索到的路径上的任何其他常规包添加到其__path__
.但在 Python 3.3 中,它还添加了命名空间包.
... the legacy behavior is to add any other regular packages on the searched path to its __path__
. But in Python 3.3, it also adds namespace packages.
所以你可以有如下的目录结构:
So you can have the following directory structure:
├── 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
一切正常.
... and as long as the two __init__.py
have the extend_path
lines (and path1
, path2
and path3
are in your sys.path
) import package.foo
, import package.bar
and import package.baz
will all work.
pkg_resources.declare_namespace(__name__)
尚未更新为包含隐式命名空间包.
pkg_resources.declare_namespace(__name__)
has not been updated to include implicit namespace packages.
相关文章