python 中的二阶导入​​问题:发生了什么?

Second order import issue in python: What is going on?

我有一个相当简单的设置:

[FOLDER]
   |-> [Lib]
          __init__.py    (__all__=["modA","modB"])
          modA.py        (contains class named classA)
          modB.py        (contains class named classB + from modA import classA)
          test1.py       (from Lib.modA import classA
                          from Lib.modB import classB)
   |-> [example]
          test2.py       (import sys
                          sys.path.append("../")
                          from Lib.modA import classA
                          from Lib.modB import classB)
来自 Lib 文件夹的

运行 test1.py 完美运行(与没有 warnings/errors/etc 一样)。另一方面,示例文件夹中的 运行 test2.py 需要系统补丁,因为 python 的工作方式很奇怪,并且从 from modA import classA(在 modB.py 中)通过 from Lib.modB import classB(在 test2.py 中)。

人们应该如何在模块中定义一个导入,以便它也可以工作,而不管可能 use/import 所述模块的任​​何未来脚本的可能位置如何?

据我了解,当您尝试手动 运行 时,没有将路径设置为环境,而当您只是这样做时 python file.py
python 不知道我们 运行ning 在哪个环境中,这正是我们 sys.path.append 将兄弟目录路径添加到 env.

的原因

如果您使用的是 IDE,如 spyder 或 pycharm。您可以选择设置程序所在的目录,或者在 pycharm 中将整个程序创建为一个包,其中所有路径都由 IDE.

处理

Python 程序应该被认为是 模块 ,而不是 目录 文件 。虽然有一些重叠,但包和模块的限制更多,但也因此封装得更好。

混合使用两者——比如手动修改 sys.path——只能作为最后的手段。

TLDR:

  • 使用完全限定的导入:from Lib.modA import classA 而不是 from modA import classA
  • 使用环境进行发现:通过 PYTHONPATH 而不是 sys.path 添加搜索路径。

首先确定哪些是 top-level 包。

这是我们从“directories/files”到“包”的地方。值得注意的是,稍后我们不能再“高于”top-level,因此它应该包含我们需要的一切。但是,我们也不能删除“下方”的任何内容,因此它应该是一个足够严格的选择。

在示例中,我们可以低至将 modA 和兄弟姐妹视为自己的 module-package,高至 FOLDER 包含整个项目。

[FOLDER]
|-> [Lib]
|   |-> modA.py
:   :

选择Lib是合理的,因为它代表了self-contained部分。升到 FOLDER 会过分,降到 modA 和兄弟姐妹会打破他们属于一起的关系。

top-level 包文件夹下的所有内容现在都属于该包。

值得注意的是,modA.py 现在是模块 Lib.modA。它不是 FOLDER.Lib.modA,也不只是 modA


仅使用绝对完全限定名称或相对名称进行导入。

既然top-level成立了,所有的import声明都得跟它说。仅通过 完全限定 名称引用模块,即使两个模块共享一个更常见的父模块也是如此:

# Lib.modB
# okay, fully qualified import
from Lib.modA import classA
# broken, unqualified import
from modA import classA

为了避免键入完整的完全限定名称,可以使用相对 名称代替。这会重新使用当前模块名称来派生要导入的模块的完全限定名称。

# Lib.modB
# okay, relative import
from .modA import classA

注意: 相对导入是 package 操作,而不是 filesystem 操作。不能超越 top-level,而是进入包命名空间。

模块内的所有内容现在都被封装并且self-contained。

无论包本身的位置如何,所有内部导入都将起作用。只要top-level可以导入,下面的都可以导入。
值得注意的是,这与可能 use/import 包的任何未来脚本的位置无关。


通过环境而不是程序启用包。

定义包的要点是获得一个代表库的 self-contained 实体。一个人可以压缩一个包裹或类似的东西,它仍然是一个包裹。

与其让脚本假定包的位置,不如让环境(脚本、用户或整台机器)公开包。基本上有两种方法可以做到这一点:

  • 公布包裹位置为搜索位置。这适合开发,因为它灵活但难以维护。 PYTHONPATH 适用于此。
  • 将包移动到Python使用的搜索位置。这适用于生产和分发,因为它需要付出努力但易于维护。 Packaging and using a package manager 适用于此。