Python3中的Python命名空间包

37

命名空间包的话题对于初学者来说有点令人困惑,而且之前的 Python 版本实现也有所不同,此外 StackOverflow 上很多问答已经过时。我正在寻找一个在 Python 3.5 或更高版本中可用的解决方案。

#场景: 我正在将一堆 Python 代码重构为模块和子模块,并致力于使每个项目在相同的命名空间中独立操作。

我们最终将使用内部 PyPi 服务器,将这些软件包提供给我们的内部网络,不希望与外部(公共)PyPi 软件包混淆。

例如: 我有两个模块,并希望能够执行以下操作:

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_mod1org_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

是否有更简单或更符合Python特色的实现方式,可以省略上述步骤?


我对存储库或setup.py不是很了解,但我可以说如果您正在使用3.5+,那么您可能根本不应该使用__init__.py - user2357112
1
是的,我阅读了PEP420,它对我来说似乎确实增加了困惑。它谈到了不同的方法,然后又说你不需要这样做,因为你可以将其添加到setup.py中。 - Bobby
嗨@Bobby,你会考虑接受一个答案吗?Rite的回答非常完整,似乎可以解决你的疑问。 - artu-hnrq
2个回答

18

虽然已经晚了,但帮助Python中的同行走过名称空间路径从未错过!

#1:

使用 __init__.py,我应该使用哪个(如果有)?:

这取决于你的需求,以下是三种做名称空间包的方法 here

  1. 使用本地名称空间包。此类型的名称空间包在PEP 420中定义,适用于Python 3.3及更高版本。如果您的命名空间中的包仅需要支持 Python 3,并通过 pip 进行安装,则建议使用此方法。

  2. 使用 pkgutil 样式的名称空间包。这适用于需要支持Python 2和3以及通过pip和python setup.py install进行安装的新软件包。

  3. 使用 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 Namespace包是解决问题的方法。这被认为是解决问题的一种Pythonic方式。

除了您提出的问题,我还发现了一些事情:

我阅读了PEP420Python Packaging guide并花了很多时间理解命名空间包,我通常了解它是如何工作的。我阅读了几个答案hereherehere以及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

编辑(08/09/2019):

为了完整回答,让我举个例子来重构一下。

以下解决方案假定使用支持隐式命名空间包的Python 3.3+

由于您正在寻找Python版本3.5或更高版本的解决方案,请采用提供的代码示例并进一步阐述。

让我们假设以下内容:

命名空间/Python包名称:org

分发包:org_clientorg_common

Python:3.3+

setuptools:40.1.0

为了完成以下操作

from org.client.client1 import mod1
from org.common import config

保持顶级目录不变,即 org_client_client1_mod1org_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()
    ...
)

第二个代码仓库:

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/

更新后的pip列表将显示以下内容:

(venv) $ pip3 list
...
org_client
org_common
...

但是它们不可导入,要导入它们,您需要遵循org.clientorg.common符号表示法。
要了解原因,您可以在此处浏览(假设在venv内部):
(venv) $ cd venv/lib/python3.5/site-packages/
(venv) $ ls -l | grep org

你会发现没有org_clientorg_common目录,它们被解释为命名空间包。

(venv) $ cd venv/lib/python3.5/site-packages/org/
(venv) $ ls -l
client/
common/
...

假设我分发了多个 org. 包,有没有一种方法可以通过使用 pip install org 从(我的个人)pypi服务器安装它们全部? - DiCaprio
1
我们通过引入一个元包 org 来解决这个问题,该包基本上是空的,并且依赖于我们所有内部的 org.* 包。这增加了另一层需要维护的内容,但也允许针对发布的分发组合进行精细化的版本控制。 - DiCaprio
那本来就是我的建议。这是一种自我引发的成本,但我必须说我同意粒度控制;共享包对于DRY最好。作为后续问题,你遇到了任何冲突吗?导入是否按预期工作?如果您先安装了单独的软件包,然后再安装此元软件包会发生什么?很想知道... - rite2hhh
实际上非常好用。由于每个发行版都被视为自己的软件包,您可以先安装 org.test 然后再安装 org。由于我们在 org 中设置了依赖项的固定版本,它只会检查本地版本是否可用,或者必须下载不同的版本。 - DiCaprio
1
还没有。文档上看起来好像没有区别,我希望是真的:D - DiCaprio
显示剩余3条评论

5
这是一个棘手的主题。到处都有-___init__.py,这并不容易让我们理解。
首先,我会回答你的问题:

使用__init__.py,我应该使用哪个(如果有)?

  • __init__.py可以完全为空,只需要放在正确的位置即可。也就是说(双关语),它们应该位于任何包含Python代码的子包中(不包括setup.py)。遵循这些规则,你应该没问题。

对于setup.py,我是否仍需要添加namespace_modules参数,如果需要,我应该使用namespace_modules=['org.common']还是namespace_modules=['org','common']

  • 不行!只有name=packages=。但是请注意packages=参数与目录结构的格式。
  • 这里是package=参数的格式:setup.py for namespace package
  • 这是相应的目录结构:example_package/a/<subpackage>

我能通过某种方式简单或更符合Python风格地实现这一点而放弃上述所有内容吗?

  • 如果您想能够单独安装多个功能,但在同一顶级命名空间下,那么您正在正确的轨道上。

我会在接下来的回答中重新实现您的命名空间包,使用本地格式:
我会将我能找到的所有有用文档放在帖子底部。
因此,我假设您想要本地命名空间包。首先让我们看一下您两个存储库的当前结构:
org_client_client1_mod1/
  setup.py
  mod1/
    __init__.py
    somefile.py

&

org_common_config/
  setup.py
  config/
    __init__.py
    someotherfile.py

我是一名有用的助手,可以为您进行文本翻译。以下是需要翻译的内容:

这太简单了!!!

获取您想要的:

我的大脑不够灵活,无法确定我们是否可以使用命名空间包进行三级深度嵌套,但为了实现您想要的功能,以下是我相当确定您想要做的事情:

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的软件包而感到困惑。
所以文档中最有用的部分是: https://packaging.python.org/guides/packaging-namespace-packages/#packaging-namespace-packages 然后这就是我如何理解这个问题的: https://github.com/pypa/sample-namespace-packages/tree/master/native

点赞包装.python.com链接,我查看了python.org上的几个页面,但没有找到那个链接。 - Mike C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接