在Python中创建命名空间包的方法
根据我在distribute中的命名空间包了解到,我可以利用命名空间包把一个大的Python包拆分成几个小的包。这真是太棒了。文档中还提到:
顺便提一下,你的项目源代码树必须包含命名空间包的
__init__.py
文件(以及任何父包的__init__.py
文件),这在正常的Python包结构中是必须的。这些__init__.py
文件必须包含以下内容:__import__('pkg_resources').declare_namespace(__name__)
这段代码确保命名空间包的机制正常工作,并且当前包被注册为命名空间包。
我在想,保持目录结构和包的层级结构一致有什么好处吗?还是说这只是distribute/setuptools的命名空间包功能的技术要求?
举个例子,
我想提供一个子包 foo.bar,所以我需要建立以下的文件夹层级,并准备一个 __init__.py
文件,以便让 setup.py
正常工作,成为命名空间包:
~foo.bar/
~foo.bar/setup.py
~foo.bar/foo/__init__.py <= one-lined file dedicated to namespace packages
~foo.bar/foo/bar/__init__.py
~foo.bar/foo/bar/foobar.py
我对命名空间包不太熟悉,但在我看来,1) 创建foo/bar文件夹和2) 准备一个几乎只有一行的 __init__.py
文件是常规任务。它们确实提供了一些“这是一个命名空间包”的提示,但我觉得我们在 setup.py 中已经有这些信息了,对吧?
编辑:
如下面的代码块所示,我能否在我的工作目录中没有嵌套目录和一行的 __init__.py
文件的情况下创建命名空间包?也就是说,我们能否通过在 setup.py 中只放一行 namespace_packages = ['foo']
来自动生成这些文件?
~foo.bar/
~foo.bar/setup.py
~foo.bar/src/__init__.py <= for bar package
~foo.bar/src/foobar.py
1 个回答
命名空间包在导入子包时特别有用。简单来说,当你导入 foo.bar
时,会发生以下事情:
- 导入器会在
sys.path
中查找类似foo
的东西。 - 一旦找到,它会在这个发现的
foo
中查找bar
。 - 如果找不到
bar
:- 如果
foo
是一个普通包,就会抛出一个ImportError
,表示foo.bar
不存在。 - 如果
foo
是一个 命名空间 包,导入器会继续在sys.path
中查找下一个匹配的foo
。只有当所有路径都查找完毕后,才会抛出ImportError
。
- 如果
这就是它的 作用,但并没有解释为什么你可能需要这样做。假设你设计了一个大型有用的库(foo
),但作为其中的一部分,你还开发了一个小而非常有用的工具(foo.bar
),其他 Python 程序员发现这个工具也很有用,即使他们并不需要整个库。
你可以把它们作为一个大包一起分发(就像你设计的那样),尽管大多数使用它的人只会导入这个子模块。这样用户会觉得非常不方便,因为他们必须下载整个包(200MB!),而他们其实只对一个10行的工具类感兴趣。如果你有开放许可证,可能会发现有几个人会把它分叉,结果你的工具模块就会有好几个不同的版本。
你可以重写整个库,让这个工具独立于 foo
命名空间(变成 bar
而不是 foo.bar
)。这样你就可以单独分发这个工具,一些用户会很高兴,但这需要很多工作,尤其是考虑到实际上有很多用户在使用整个库,他们也需要重写程序来适应新的结构。
所以你真正想要的是一种方法,可以单独安装 foo.bar
,同时在需要时又能与 foo
和谐共存。
命名空间包正好可以做到这一点,两个完全独立的 foo
包可以共存。setuptools
会识别这两个包是设计成可以并排放置的,并会礼貌地调整文件夹/文件的结构,使得两个包都在路径上,并显示为 foo
,一个包含 foo.bar
,另一个包含其余的 foo
。
你将会有两个不同的 setup.py
脚本,每个包一个。两个包中的 foo/__init__.py
都需要表明它们是命名空间包,以便导入器知道无论哪个包先被发现都要继续处理。