简单的理解,命名空间包是对相关的包或模块进行分组的一种方法,通常是一个被忽略的功能,它对于在较大的项目中构建打包生态系统非常有用。
如果你的应用组件的开发、打包和版本化都是独立的,但仍然希望从同一个命名空间访问它们,那么命名空间包特别有用,它有利于明确每个包所属的组织或项目。
例如,假设有一个 Acme 公司,该公司中使用共同的 acme 命名空间,同时创建通用的 acme 命名空间包作为该组织的其他包的容器。如果 Acme 公司中的某人想要向这个命名空间贡献一个与 SQL 相关的库,那么他需要在 acme 中注册自己新的 acme.sql 包,整个文件结构如下所示:
$tree acme/ acme/ ├───acme │ ├───__init__.py │ └───sql │ └──__init__.py └───setup.py 2 directories, 3 files
在此基础上,如果想添加一个新的子包,例如添加 templating,则需要将其包含在 acme 的源代码树中,如下所示:
$tree acme/ acme/ ├───acme │ ├───__init__.py │ ├───sql │ │ └──__init__.py │ └───templating │ └──__init__.py └───setup.py 3 directories, 4 files
仔细观察就会发现,采用这种方式几乎不可能单独开发 acme.sql 和 acme.templating。且 setup.py 脚本还必须指定每个子包的所有依赖,所以不可能(至少非常困难)选择性地安装 acme 中的部分组件。此外,如果某些子包的需求文件有冲突,是一个无法解决的问题。
通过利用命名空间包,我们可以单独保存每个子包的源代码树,如下所示:
$tree acme.sql/ acme.sql/ ├───acme │ └───sql │ └──__init__.py └───setup.py 2 directories, 2 files $tree acme.templating/ acme.templating/ ├───acme │ └───templating │ └──__init__.py └───setup.py 2 directories, 2 files
由此,就可以在 PyPI 或者使用的任何包索引中单独注册它们,用户还可以从 acme 命名空间中选择想要安装的子包,而无需安装通用的 acme 包,执行代码如下:
$pip install acme.sql acme.templating
注意,独立的源代码树不足以在 Python 中创建命名空间包,如果不想让包之间相互覆盖,就需要做一些额外的工作,此外,正确的处理方式也会随着 Python 版本的不同而有所不同。
隐式命名空间包
如果你只使用 Python 3.x,也只面向 Python 3.x 的用户,则可以使用 PEP 420 引入的定义命令空间的新方法,即隐式命名空间包。它是标准路径的一部分,并从 Python 3.3 版本开始成为语言官方内容的一部分。
简单来说,对于每一个包含 Python 包或模块(也包括命名空间包)的目录来说,如果其不包含 __init__().py 文件,那么它就被看做是命名空间包。
例如,前面所说的 acme 在 Python 3.3 以及更高版本中,就是一个命名空间包。使用安装工具的最小 setup.py 脚本文件如下所示:
from setuptools import setup setup( name = 'acme.templating', packages = ['acme.templating'], )
但是,直到发表本节时,setuptools.find_packages() 还不支持 PEP 420,但这在未来很可能会改变。此外,要想实现命名空间包的简单继承,显示地定义包列表是值得的。
以前Python版本中的命名空间包
Python 3.3 之前的版本中,虽无法使用 PEP 420 布局中的命名空间包,但仍可以使用它。旧版 Python 中,有几种方法可以将包定义成命名空间。
最简单的方法就是为每个组件创建一个文件结构,类似于没有命名空间包的普通包布局,并将所有事情都留给 setuptools。
因此,acme.sql 和 acme.templating 的布局示例可能如下所示:
$tree acme.sql/ acme.sql/ ├───acme │ ├──__init__.py │ └───sql │ └──__init__.py └───setup.py 2 directories, 3 files $tree acme.templating/ acme.templating/ ├───acme │ ├──__init__.py │ └───templating │ └──__init__.py └───setup.py 2 directories, 3 files
注意,acme.sql 和 acme.templating 都有一个额外的源代码文件 acme/__init__.py,这个文件必须是空的。
如果我们提供 acme 作为 setuptools.setup() 函数 namespace_package 关键字参数的值,那么将会创建如下的 acme 命名空间包:
from setuptools import setup setup( name = 'acme.templating', packages = ['acme.templating'], namespace_package = ['acme'], )
当然,最简单的方法不一定是最好的,为了注册一个新的命名空间,setuptools 将会在 __init__.py 文件中调用 pkg_resources.declare_namespace() 函数,即便 __init__.py 文件是空的也会调用。
无论如何,正如官方文档所说,你自己负责在 __init__.py 文件中声明命名空间,并且未来可能会删除 setuptools 的这个隐式行为。为了保证安全,也为了未来依然可用(future-proof),需要将下面这行代码添加到 acme/__init__.py 文件中:
__import__('pkg_resources').declare_namespace(__name__)
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/23054.html