35

命名空间包的主题对于外行来说似乎有点令人困惑,而且以前版本的 Python 已经以几种不同的方式实现了它,或者 StackOverflow 上的很多问答都过时了,这无济于事。我正在寻找解决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_mod1and 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”的东西?

4

2 回答 2

14

迟到了,但在 Python 中帮助其他旅行者沿着命名空间路径走下去永远不会受到伤害!

#1:

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

这取决于,这里列出了三种方法来执行命名空间包:

  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']?

如果您选择的命名空间包不是原生样式,那么是的,您将需要namespace_packagessetup().

#3:

我可以通过以某种不同的方式实现这一点来放弃上述所有内容吗?也许更简单或更“pythonic”的东西?

由于您最终在 python 中遇到了一个复杂的主题,因此您似乎知道自己在做什么,想要什么,并确定创建 Python 命名空间包是实现它的方法。这将被认为是解决问题的pythonic方法。


除了您的问题之外,我还发现了以下几点:

我阅读了 PEP420Python 打包指南并花了很多时间来理解命名空间包,我大致了解它是如何工作的。我在这里这里这里阅读了几个答案,以及关于 SO 的这个线程——这里的示例和Rob 共享的 Git链接。

然而,我的问题是在我创建了我的包之后。由于所有说明和示例代码都明确列出了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.clientorg.common符号。

要了解原因,您可以浏览此处(假设在 venv 内部):

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

您会看到没有org_clientororg_common目录,它们被解释为命名空间包。

(venv) $ cd venv/lib/python3.5/site-packages/org/
(venv) $ ls -l
client/
common/
...
于 2019-07-24T17:00:27.637 回答
4

这是一个艰难的话题。无处不在的所有-' _s 和__init__.py' 并不完全让我们感到轻松。

首先,我会回答你的问题:

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

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

使用 setup.py,我还需要添加namespace_modules参数吗?如果需要,我会使用namespace_modules=['org.common'], 还是namespace_modules=['org', 'common']?

  • 没有!只有name=packages=packages=但是,请注意与目录结构相比的 arg格式。
  • 这是package=arg的格式:命名空间包的 setup.py
  • 对应的目录结构如下:example_package/a/<子包>

我可以通过以某种不同的方式实现这一点来放弃上述所有内容吗?也许更简单或更“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

基本上,关键name=packages=stuptools.setup()每个setup.py.

这些将是:

name='org_client',
...
packages=['org.client']

&

name='org_common'
...
packages['org.common']

分别。

然后只需pip install .在每个顶级目录中安装每个。

安装第一个将使您可以访问该somefile.py模块,安装第二个将使您可以访问someotherfile.py. 它也不会因为您尝试安装org在同一环境中命名的 2 个软件包而感到困惑。

K 所以文档中最有用的部分: https ://packaging.python.org/guides/packaging-namespace-packages/#packaging-namespace-packages

然后这就是我实际上是如何理解这一点的:https ://github.com/pypa/sample-namespace-packages/tree/master/native

于 2018-08-07T01:30:22.550 回答