【问题标题】:How to structure imports inside project to work for both scripts and modules?如何在项目内部构建导入以同时适用于脚本和模块?
【发布时间】:2022-06-14 23:08:47
【问题描述】:

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

[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 可以完美运行而不会出现错误。另一方面,从示例文件夹运行 test2.py 需要 sys-patch 才能找到 Lib;但是,它随后崩溃,No module named modA 通过test2.py 中的from Lib.modB import classB 追溯到modB.py 中的from modA import classA

应该如何在一个模块中定义一个导入,这样它也可以工作,而不管将来可能使用/导入该模块的任何脚本的可能位置如何?

【问题讨论】:

  • 让 lib 成为可以导入的实际包?
  • 导入路径应由环境设置——通过安装、PYTHONPATH 或类似方式——而不是由程序本身——通过sys.path 或类似方式。后者仅用于元编程并在凌晨 2 点的紧迫期限内完成工作。
  • FWIW(在回答中将忽略这一点)我也不知道test1.py 会如何工作。它使用modA.py both Lib.modAmodA。这只会在手动修改导入路径时“起作用”,并导致程序状态不正确,因为事物实际上存在两次。
  • @Sayse & MisterMiyagi,确实如此,但我目前正在开发这个包,这使得安装和包的想法有点循环
  • @MisterMiyagi test1.py 在我阅读时只使用了一次 modA。它从 lib.modA(.py) 导入 modA

标签: python import python-module


【解决方案1】:

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

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

TLDR:

  • 使用基于包的合格导入:
    from Lib.modA import classA/from .modA import classA 而不是from modA import classA
  • 使用环境进行发现:
    通过PYTHONPATH而不是sys.path添加搜索路径。

首先确定哪些是顶级包。

这是我们从“目录/文件”到“包”的地方。值得注意的是,我们以后不能“超越”顶层,所以它应该包含我们需要的一切。但是,我们也不能删除“下面”的任何内容,因此它应该是一个足够严格的选择。

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

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

选择Lib 是合理的,因为它代表一个独立的部分。高到FOLDER 会过分,低到modA 和兄弟姐妹会破坏他们的归属关系。

顶级包文件夹下的所有内容现在都属于包。

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


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

现在顶层已经建立,所有的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

注意:相对导入是操作,而不是文件系统操作。不能超越顶层,而是下放到包命名空间。

模块内的所有内容现在都已封装并自包含。

无论包本身的位置如何,所有合格的导入都将起作用。只要顶层可以导入,它下面的所有东西也可以导入。
值得注意的是,无论将来可能使用/导入包的任何脚本的位置如何,这都有效。


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

定义包的目的是获得一个表示库的单一、自包含的实体。一个人可以压缩一个包裹或类似的东西,它仍然是一个包裹。
作为回报,这意味着代码本身不应该通过直接添加/更改目录来搜索包来破坏这种抽象。

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

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

【讨论】:

  • from .modA import modA 可以解决问题。当超越绝对琐碎的例子时,我总是惊讶于 python 的设计是多么不必要的复杂。也感谢您提供其他信息。我可能会在完成工作时需要它。
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-04
    • 2020-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-09
    • 2020-10-21
    • 2021-07-02
    相关资源
    最近更新 更多