项目结构导致冗余点符号

Project structure leads to redundant dot notation

我创建了一个 Python 包,它建立在 Kenneth Reitz 的 "Repository Structure and Python" (1) 中指示的结构之上。主包路径为:

/projects-folder (not site-packages)
    /package
        /package
            __init__.py
            Datasets.py
            Draw.py
            Gmaps.py
            ShapeSVG.py
            project.py
        __init__.py
        setup.py

对于当前结构,我必须使用以下模块导入语法:

import package.package.Datasets

我希望输入以下内容:

import package.Datasets

当然,我能够将同一个词输入两次,但从更深层次的意义上感觉不对,即我错误地构建了我的包,或者误解了 Python 如何解释该结构。

根据文档 (2),Python 需要外部 __init__.py 才能完全检测到此包。但这将 /package/ 设置为包的顶层,将 /package/package/ 设置为子包,迫使我使用上面笨拙的导入语法。

为了避免这种情况,我的选择似乎是:

然而,对于本来不应该成为问题的事情,这两种方法似乎都不是最佳解决方法。我该怎么办?

虽然我不完全同意您的包结构,但您可以使用 __all__ 并且可能是我迄今为止看到的 star 导入的一种合法用途。 __init__.py 不仅仅是将您的文件夹识别为包或子包,还有更多用途。

使用星标导入

package/package/__init__.py 中,添加一个变量 __all__ 来声明您要导出的所有 public 元素:

__all__ = ['Datasets', 'Draw', 'Gmaps', 'ShapeSVG', 'project']

package/__init__.py 中执行 from package.package import *。现在所有作为 package.package.x 可用的属性也将作为 package.x.

可用

如果您想直接将 package.package.__all__ 复制到 package.__all__(这是可选的,但可以让您正确地执行 from package import *),您可以执行类似

from package.package import *
from package.package import __all__ as _all
__all__ = _all
del _all

未使用星号导入

您完全可以不使用 package.package.__all__ 来完成同样的事情。只需将 __all__ 直接添加到 package/__init__.py 并使用 from package.package import x 样式的导入:

from package.package import (
    Datasets, Draw, Gmaps, ShapeSVG, project
)
# As before, package.__all__ is optional
__all__ = ['Datasets', 'Gmaps', 'ShapeSVG', 'project']

我仍然建议使用 package.package.__all__ 变量,但对于此特定用途它是可选的。

优缺点

这两种方法都非常合理,我已经在主要项目中看到过这两种方法。第一种方法减少了冗余。您只在一处定义 public 导出:package.package.__all__。星号导入并 package.__all__ 直接引用该定义,导致您真正需要维护的地方。另一方面,有时您希望将 "full" package.package.x API 与通过 package.x 公开给临时用户的内容分开。在这种情况下,请选择第二个选项。这里唯一的缺点是你必须更加小心地保持 package.__all__ 和相应的导入正确同步。

备注

我见过的许多项目(尤其是 numpy 想到的)都使用这种技术将各个模块的属性导出到顶层。例如,如果您有一个函数 package.package.Datasets.get_data,它将列在 package.package.Datasets.__all__ 中,它将被导入 pacakge.package.__init__,附加到 package.package.__all__,然后被顶级包和 package.__all__.

你误会了。由于某种原因,您有两个 package 包,但您引用的消息来源从未说过要这样做。 setup.py 的外部文件夹不应是包。

听起来您正在 运行ning Python projects-folder 并尝试从那里导入您的包。那不是你应该做的。您有多种选择可以将您的包裹放入导入系统。 (我将里面有setup.py的文件夹称为setupfolder,以区别于内部文件夹):

  • 使用 setup.py 构建您的包,例如 python setup.py bdist-wheel --universal,并使用 pip 安装构建的包。
  • 跳过构建步骤,直接 运行 pip install path/to/setupfolder。如果你想分发你的包,构建包会产生一个有用的安装程序,但也许你不想这样做。
  • "Install" 开发模式下包的源代码树 pip install -e path/to/setupfolder,因此 Python 导入系统将在执行导入时定位包的源代码树。这很方便,因为如果您编辑源存储库,则不必重建和重新安装,尽管您仍然需要重新启动任何正在使用该软件包的 运行ning Python 进程。
  • 运行 Python 直接来自 setupfolder.

这些选项中的任何一个都会导致您的包可以直接作为 package 而不是 package.package 导入。