Python Python3 中的命名空间包

Python Namespace Packages in Python3

命名空间包的主题对于外行来说似乎有点混乱,Python 的早期版本已经以几种不同的方式实现了它,或者 Whosebug 上的很多问答也无济于事过时了。我正在 Python 3.5 或更高版本 .

中寻找解决方案

#场景: 我正在将一堆 Python 代码重构为模块和子模块,并努力让这些项目中的每一个都设置为在位于同一命名空间中时彼此独立运行。

我们最终将使用内部 PyPi 服务器,将这些包提供给我们的内部网络,并且不想将它们与外部 (public) 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_mod1org_common_config,所以我相信我只需要对打包和 __init__.py 文件执行设置。

问题:

#1

With the __init__.py, which of these should I be using (if any)?:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

Or:

import pkg_resources
pkg_resources.declare_namespace(__name__)

#2

With setup.py, do I still need to add the namespace_modules parameter, and if so, would I use namespace_modules=['org.common'], or namespace_modules=['org', 'common']?

#3

Could I forgo all of the above by just implementing this differently somehow? Perhaps something simpler or more "pythonic"?

这是一道难题。 -___init__.py 无处不在,这对我们来说并不容易。

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

With the __init__.py, which of these should I be using (if any)?

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

With setup.py, do I still need to add the namespace_modules parameter, and if so, would I use namespace_modules=['org.common'], or namespace_modules=['org', 'common']?

  • 不!只有 name=packages=。但是,请注意与目录结构相比 packages= arg 的格式。
  • 这里是 package= arg 的格式:
  • 对应的目录结构如下:

Could I forgo all of the above by just implementing this differently somehow? Perhaps something simpler or more "pythonic"?

  • 如果您希望能够单独安装多个功能,但在同一个顶级名称空间下,那么您就走对了。

我将用这个答案的其余部分以本机格式重新实现您的命名空间包:

我会把我能找到的所有有用的文档放在 post 的底部。

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= 参数指定给每个 setup.py 中的 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

晚会迟到了,但在 Python!

#1:

With the __init__.py, which of these should I be using (if any)?:

视情况而定,列出的命名空间包有三种方法 here:

  1. Use native namespace packages. This type of namespace package is defined in PEP 420 and is available in Python 3.3 and later. This is recommended if packages in your namespace only ever need to support Python 3 and installation via pip.

  2. Use pkgutil-style namespace packages. This is recommended for new packages that need to support Python 2 and 3 and installation via both pip and python setup.py install.

  3. Use pkg_resources-style namespace packages. This method is recommended if you need compatibility with packages already using this method or if your package needs to be zip-safe.

如果您使用的是#2 (pkgutil-style) 或#3 (pkg_resources-style),那么您将不得不为 __init__.py 文件使用相应的样式。如果您使用本机命名空间,则命名空间目录中没有 __init__.py

#2:

With setup.py, do I still need to add the namespace_modules parameter, and if so, would I use namespace_modules=['org.common'], or namespace_modules=['org', 'common']?

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

#3:

Could I forgo all of the above by just implementing this differently somehow? Perhaps something simpler or more "pythonic"?

由于您在 python 中结束了一个复杂的主题,看来您知道自己在做什么,想要什么并确定创建 Python 命名空间包是可行的方法它。这将被视为解决问题的 pythonic 方法。


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

我阅读了 Rob 分享的 PEP420, the Python Packaging guide and spent a lot of time understanding the namespace packages, and I generally understood how it worked. I read through a couple of answers here, here, here, and this thread on SO as well - the example here and on the Git link

然而,我的问题是在创建包之后。由于所有说明和示例代码都在 setuptools.setup(package=[]) 函数中明确列出了包,因此我的代码失败了。我的 sub-packages/directories 不包括在内。深入挖掘,我发现 setuptools 有一个 find_namespace_package() 函数也有助于添加子包

编辑:

Link 到 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 或更高版本的解决方案,让我们使用提供的代码示例并进一步详细说明。

让我们假设如下:

Namespace/Python 包名:org

分发包:org_clientorg_common

Python: 3.3+

设置工具: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()
    ...
)

存储库 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/

更新的 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/
...