如何构建项目内部的导入以适用于脚本和模块?

发布于 2025-01-13 12:26:53 字数 942 浏览 0 评论 0原文

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

[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;然而,它随后崩溃,并通过 from Lib 追溯到 modB.py 中的 from modA import classANo module named modA 。 modB 在 test2.py 中导入 classB

应该如何在模块中定义导入,以便无论可能使用/导入所述模块的任​​何未来脚本的可能位置如何,它都可以正常工作?

I have a rather simple setup:

[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)

Running test1.py from the Lib folder works perfectly without errors. Running test2.py from the example folder on the other hand requires the sys-patch to find Lib at all; however, it then crashes with No module named modA tracing back to the from modA import classA in modB.py via from Lib.modB import classB in test2.py.

How is one supposed to define an import in a module such that it will also work irrespective of the possible location of any future script that may use/import said module?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

做个ˇ局外人 2025-01-20 12:26:53

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

注意:相对导入是操作,而不是文件系统操作。人们无法超越顶层,但只能深入到包命名空间。

模块内的所有内容现在都已封装且独立。

所有合格的导入都将起作用,而不管包本身的位置如何。只要顶层可以导入,那么它下面的所有内容也都可以导入。
值得注意的是,无论未来可能使用/导入该包的脚本的位置如何,这都有效。

使用绝对限定名称运行打包代码

在包内执行代码时,Python 必须知道该代码是包的一部分。因此,它必须以其绝对限定名称运行。使用 -m 开关 来执行此操作:

# import and execute Lib.test1
python3 -m Lib.test1

通过以下方式启用包 :环境,而不是程序。

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

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

  • 宣布包位置作为搜索位置。这适合开发,因为它灵活但难以维护。 PYTHONPATH 适用于这。
  • 将包移动到 Python 使用的搜索位置。这适合生产和分发,因为它需要努力但易于维护。 打包并使用包管理器适合于此。

Python programs should be thought of as packages and modules, not as directories and files. While there is some overlap, packages and modules are more restrictive but also better encapsulated as a result.

Mixing both – say by manually modifying sys.path – should only be done as a last resort.

TLDR:

  • Use qualified imports based on the package:
    from Lib.modA import classA/from .modA import classA instead of from modA import classA.
  • Use the environment for discovery:
    Add search paths via PYTHONPATH instead of sys.path.

Start by deciding which ones are the top-level packages.

This is the point where we go from "directories/files" to "package". Notably, we cannot go "above" the top-level later on, so it should contain everything we need. However, we cannot remove anything "below" either, so it should be a tight enough selection.

In the example we could go as low as treating modA and siblings as their own module-package, and as high as FOLDER encompassing the entire project.

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

It is reasonable to pick Lib since it represents a self-contained part. Going higher to FOLDER would be excessive, going lower to modA and siblings would break their relation as belonging together.

Everything under the top-level package folder now belongs to the package.

Prominently, FOLDER/Lib/modA.py is now the module Lib.modA. It is not FOLDER.Lib.modA, nor just modA.


Import package content only with absolute or relative qualified names.

Now that the top-level is established, all import statements must be made with regard to it. Refer to modules by their qualified name only, even when two modules share a more common parent:

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

In order to avoid typing out the entire fully qualified name, one can use a relative name instead. This reuses the current module name to derive the fully qualified name of the module to import.

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

Note: Relative imports are a package operation, not a filesystem operation. One cannot go beyond the top-level, but descend into package namespaces.

Everything inside the module is now encapsulated and self-contained.

All qualified imports will work irrespective of the location of the package itself. As long as the top-level can be imported, everything below it can be imported as well.
Notably, this works irrespective of the location of any future script that may use/import the package.

Run packaged code with absolute qualified names

When executing code inside a package, Python has to know that the code is part of the package. Thus, it must be run with its absolute qualified named. Use the -m switch to do so:

# import and execute Lib.test1
python3 -m Lib.test1

Enable packages via the environment, not the program.

The point of defining a package is getting a single, self-contained entity that represents a library. One could zip up a package or similar, and it would still be a package.
In return, this means that the code itself should not break this abstraction by directly adding/changing directories to search packages.

Instead of having scripts assume the location of the package, the environment - of the script, the user, or the entire machine – should expose the package. There are basically two ways to do this:

  • Announce the package location as a search location. This is suitable for development as it is flexible but hard to maintain. The PYTHONPATH is appropriate for this.
  • Move the package to a search location used by Python. This is suitable for production and distribution as it requires effort but is easy to maintain. Packaging and using a package manager is appropriate for this.
情栀口红 2025-01-20 12:26:53

据我了解,当您尝试手动运行时,没有设置路径作为环境,而当您这样做时
python 文件.py
python 不知道我们在哪个环境中运行,这正是我们执行 sys.path.append 将同级目录路径添加到 env 的原因。

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

To what I understand, when you are trying to run manually there is no path set as the environment, and when you just do
python file.py
python doesn't know in which environment we are running, which is precisely why, we do sys.path.append, to add the sibling directory path to env.

In the case if you are using an IDE like spyder or pycharm. you have an option to set the directory in which your program is located, or in pycharm it creates the whole program as a package where all the paths are handled by IDE.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文