Python3 中的 Python 命名空间包
Posted
技术标签:
【中文标题】Python3 中的 Python 命名空间包【英文标题】:Python Namespace Packages in Python3 【发布时间】:2017-05-28 00:42:56 【问题描述】:命名空间包的话题对于外行来说似乎有点混乱,而且以前版本的 Python 已经以几种不同的方式实现它或者 *** 上的很多问答都过时了,这无济于事。我正在 Python 3.5
或更高版本中寻找解决方案。
场景:
我正在将一堆 Python 代码重构为模块和子模块,并努力将这些项目中的每一个设置为在同一个命名空间中相互独立运行。
我们最终将使用内部 PyPi 服务器,将这些包提供给我们的内部网络,并且不想将它们与外部(公共)PyPi 包混淆。
示例:我有 2 个模块,我希望能够执行以下操作:
from org.client.client1 import mod1
from org.common import config
反射的模块将是分开的:
存储库 1:
org_client_client1_mod1/
setup.py
mod1/
__init__.py
somefile.py
存储库 2:
org_common_config/
setup.py
config/
__init__.py
someotherfile.py
我的 Git 存储库已经设置为 org_client_client1_mod1
和 org_common_config
,所以我只需要对打包和 __init__.py
文件执行设置,我相信。
问题:
#1
对于
__init__.py
,我应该使用哪一个(如果有的话)?:from pkgutil import extend_path __path__ = extend_path(__path__, __name__)
或者:
import pkg_resources pkg_resources.declare_namespace(__name__)
#2
使用
setup.py
,是否还需要添加namespace_modules
参数,如果需要,是否使用namespace_modules=['org.common']
, 或namespace_modules=['org', 'common']
?
#3
我可以通过以某种不同的方式实现这一点来放弃上述所有内容吗?也许更简单或更“pythonic”的东西?
【问题讨论】:
我不太了解存储库或 setup.py,但我可以说you probably shouldn't be using an__init__.py
at all if you're on 3.5+。
是的,我阅读了 PEP420,它似乎确实增加了我的困惑。它谈到了方法,然后说你不需要这样做,因为你可以将它添加到setup.py
。
嘿@Bobby,你会考虑接受答案吗? Rite's one 很完整,似乎解决了你的疑惑
【参考方案1】:
聚会迟到了,但在 Python 中帮助其他旅行者沿着命名空间路径走下去永远不会受到伤害!
#1:
对于
__init__.py
,我应该使用哪一个(如果有的话)?:
这取决于here 列出的命名空间包的三种方法:
使用本地命名空间包。这种类型的命名空间包在 PEP 420 中定义,在 Python 3.3 及更高版本中可用。如果您的命名空间中的包只需要支持 Python 3 并通过 pip 安装,则建议这样做。
使用 pkgutil 风格的命名空间包。对于需要支持 Python 2 和 3 并通过 pip 和 python setup.py install 安装的新软件包,建议使用此方法。
使用 pkg_resources 风格的命名空间包。如果您需要与已经使用此方法的包兼容,或者您的包需要是 zip 安全的,建议您使用此方法。
如果您使用的是#2 (pkgutil-style
) 或#3 (pkg_resources-style
),那么您必须为__init__.py
文件使用相应的样式。如果您使用本机命名空间,则命名空间目录中没有 __init__.py
。
#2:
使用 setup.py,是否还需要添加 namespace_modules 参数,如果需要,我是使用 namespace_modules=['org.common'] 还是 namespace_modules=['org', 'common']?
如果您选择的命名空间包不是原生样式,那么是的,您需要在您的 setup()
中使用 namespace_packages
。
#3:
我可以通过以某种方式实现这一点来放弃上述所有内容吗?也许更简单或更“pythonic”?
由于您最终在 python 中遇到了一个复杂的主题,因此您似乎知道自己在做什么,想要什么,并确定创建 Python 命名空间包是实现它的方法。这将被视为解决问题的 Python 方法。
除了您的问题之外,我还发现了以下几点:
我阅读了PEP420、Python Packaging guide 并花了很多时间了解命名空间包,我大致了解它是如何工作的。我阅读了几个答案here、here、here,以及这个关于 SO 的帖子——这里的示例和 Rob 共享的 Git link 上的示例。
然而,我的问题是在我创建了我的包之后。由于所有说明和示例代码都在 setuptools.setup(package=[])
函数中明确列出了该包,因此我的代码失败了。我的子包/目录不包括在内。深入挖掘,我发现 setuptools 有一个 find_namespace_package()
函数,它也有助于添加子包
编辑:
链接到find_namespace_packages()
(setuptools
版本高于40.1.0
):https://setuptools.readthedocs.io/en/latest/setuptools.html#find-namespace-packages
编辑(2019 年 8 月 9 日):
为了完成答案,让我也用一个例子来重构。
以下解决方案假设 Python 3.3+ 支持隐式命名空间包
由于您正在寻找 Python 版本 3.5
或更高版本的解决方案,让我们使用提供的代码示例并进一步详细说明。
假设如下:
命名空间/Python 包名:org
分发包:org_client
、org_common
Python:3.3+
安装工具:40.1.0
为您执行以下操作
from org.client.client1 import mod1
from org.common import config
并保持您的***目录相同,即。 org_client_client1_mod1
和 org_common_config
,您可以将结构更改为以下内容
存储库 1:
org_client_client1_mod1/
setup.py
org/
client/
client1/
__init__.py
submod1/
__init__.py
mod1/
__init__.py
somefile.py
file1.py
更新setup.py
from setuptools import find_namespace_packages, setup
setup(
name="org_client",
...
packages=find_namespace_packages(), # Follows similar lookup as find_packages()
...
)
存储库 2:
org_common_config/
setup.py
org/
common/
__init__.py
config/
__init__.py
someotherfile.py
更新setup.py
:
from setuptools import find_namespace_packages, setup
setup(
name="org_common",
...
packages=find_namespace_packages(), # Follows similar lookup as find_packages()
...
)
安装(使用pip
):
(venv) $ pip3 install org_common_config/
(venv) $ pip3 install org_client_client1_mod1/
更新的点子列表将显示以下内容:
(venv) $ pip3 list
...
org_client
org_common
...
但它们不可导入,要导入,您必须遵循 org.client
和 org.common
表示法。
要了解原因,您可以在此处浏览(假设在 venv 内部):
(venv) $ cd venv/lib/python3.5/site-packages/
(venv) $ ls -l | grep org
您会看到没有org_client
或org_common
目录,它们被解释为命名空间包。
(venv) $ cd venv/lib/python3.5/site-packages/org/
(venv) $ ls -l
client/
common/
...
【讨论】:
假设我分发了多个org.
包,有没有办法可以使用pip install org
从(我的个人)pypi 服务器安装它们?
这是一个好问题,但恐怕我没有答案,重新阅读 PEP420 文档 (python.org/dev/peps/pep-0420/#terminology),似乎是拥有单独的“分发”包的全部意义会被击败(尽管我可以理解为什么这很吸引人)。我不相信 pip 支持索引包的通配符。我们使用操作系统包而不是***进行分发。既然你有一个内部的 PyPI,你不妨试试
我试过了,我知道 OpenStack 有命名空间包(oslo.*),pip 没有安装抱怨找不到任何包。它实际上永远不会工作,在这种情况下版本将如何工作?如果不同的发行版有不同的版本/方案,那将是一场噩梦
我们通过引入一个元包org
解决了这个问题,它基本上是空的并且依赖于我们所有的内部org.*
包。这增加了另一个必须维护的层,但也允许对已发布的发行版组合进行细粒度的版本控制。
不是喷气机。文档看起来没有区别,我希望这是真的:D【参考方案2】:
这是一个棘手的话题。所有-
's、_
's 和__init__.py
's 无处不在,这对我们来说并不容易。
首先,我会回答你的问题:
对于
__init__.py
,我应该使用哪一个(如果有的话)?
__init__.py
可以完全为空,只需在正确的位置即可。即(双关语)它们应该在任何包含 python 代码的子包中(不包括setup.py
。)遵循这些规则,你应该没问题。
不!只有使用 setup.py,是否还需要添加
namespace_modules
参数,如果需要,我是使用namespace_modules=['org.common']
,还是使用namespace_modules=['org', 'common']
?
name=
和packages=
。但是,请注意 packages=
参数的格式与目录结构的比较。
这是package=
arg 的格式:
下面是对应的目录结构:
如果您希望能够单独安装多个功能,但在同一个***命名空间下,那么您就在正确的轨道上。我可以通过以某种方式实现这一点来放弃上述所有内容吗?也许更简单或更“pythonic”?
我将在此答案的其余部分以本机格式重新实现您的命名空间包:
我会将所有能找到的有用文档放在帖子底部。
K 所以我假设你想要本地命名空间包。首先让我们看看你的 2 个 repos 的当前结构:
org_client_client1_mod1/
setup.py
mod1/
__init__.py
somefile.py
&
org_common_config/
setup.py
config/
__init__.py
someotherfile.py
这^太容易了!!!
得到你想要的:
我的大脑不够灵活,无法知道我们是否可以使用命名空间包进行 3 级深度,但要做到你想做的事,我敢肯定你想做的事如下:
org-client/
setup.py
org/
client/
client1/
__init__.py
mod1/
__init__.py
somefile.py
&
org-common-but-also-note-this-name-doesnt-matter/
setup.py
org/
common/
__init__.py
config/
__init__.py
someotherfile.py
基本上,关键是在每个setup.py
中将正确的name=
和packages=
参数指定为stuptools.setup()
。
这些将是:
name='org_client',
...
packages=['org.client']
&
name='org_common'
...
packages['org.common']
分别。
然后在每个***目录中安装每个pip install .
。
安装第一个将允许您访问somefile.py
模块,安装第二个将允许您访问someotherfile.py
。它也不会因为您尝试在同一环境中安装 2 个名为 org
的软件包而感到困惑。
K 所以文档中最有用的部分:https://packaging.python.org/guides/packaging-namespace-packages/#packaging-namespace-packages
然后我是这样理解的:https://github.com/pypa/sample-namespace-packages/tree/master/native
【讨论】:
以上是关于Python3 中的 Python 命名空间包的主要内容,如果未能解决你的问题,请参考以下文章
如何使用标准库包名称解决 Python 包中的命名空间冲突?