Python导入树中的顶层模块在哪里?如何定义它?

1 投票
2 回答
39 浏览
提问于 2025-04-13 00:38

我在做一个涉及多个文件的项目,遇到了一些关于导入的麻烦。我看了大约20篇文章和Stack Overflow上的问题,但没有一篇能解释这种情况。

假设我的文件夹结构是这样的:

\package
    \folder_a
        __init__.py
        b.py
    __init__.py
    a.py

b.py里有一个简单的hello函数。在a.py中,我写了from package.folder_a.b import hello。当我运行a.py时,出现了错误“ModuleNotFoundError: no module named 'package'”。

为什么会这样呢?当运行a.py时,package不是顶层目录吗?

我找到了一种解决方法,就是把a.py中的代码改成from folder_a.b import hello,但这样又会出现另一系列问题。假设我正在处理一个稍微复杂一点的文件结构(其实我就是),比如:

\package
    \folder_a
        __init__.py
        b.py
    \folder_b
        __init__.py
        c.py
    __init__.py
    a.py

在这里,我想把hello导入到c.py中。无论是from package.folder_a.b import hello还是from folder_a.b import hello,都会出现同样的“ModuleNotFoundError: no module named 'package/folder_a'”。我猜这是因为在执行文件时,文件没有把那些文件夹添加到命名空间中(从a.py导入c.py时可以正常工作)。有没有办法强制让\package成为顶层模块,而不需要从它自己目录下的文件运行脚本呢?希望这样说你能明白。谢谢。

2 个回答

0

Python把你正在运行的脚本所在的文件夹当作最顶层的目录。举个例子,当你在文件夹folder_b里执行b.py时,Python会把/folder_b当作顶层目录,而不是/package。

为了处理你的情况,你可以使用相对导入。下面是如何在a.py和c.py中调整你的导入方式:

文件a.py ->
from .folder_a.b import hello

文件c.py ->
from ..folder_a.b import hello

这样做可以让Python正确找到模块的路径,无论你当前的工作目录是什么,或者你正在执行的脚本在哪里。

1

为了避免混淆,我们来重新命名一下package,因为这里的意思并不是定义一个包含名为a的模块的包。你有一个项目,这个项目定义了三样东西:

  1. 一个顶层包,里面有一个名为b的模块
  2. 一个顶层包,里面有一个名为c的模块
  3. 一个名为a.py脚本

或者你也可以定义两样东西:

  1. 一个顶层包package,里面有两个子包p1p2
  2. 一个名为a.py的脚本。

所以你的源代码目录可能看起来像这样:

\project
    \p1
        b.py
    \p2
        c.py
    a.py

或者这样:

\project
    \package
        \p1
            b.py
        \p2
            c.py
    a.py

(除非你真的有代码要放在__init__.py文件里,否则很久以来就不需要这些文件来定义一个包了。)

当你从一个发行包中安装代码时(比如,用pip install myproject在虚拟环境中安装),那么a.py会被放到一个合适的bin目录里,而两个包p1p2会被放到一个合适的site-packages目录里。例如,假设你选择了上面的第一个选项:

$ python3 -m venv venv
$ venv/pip/install myproject
[...]
$ ls venv/bin/a.py
venv/bin/a.py
$ ls -d venv/lib/python3.12/site-packages/p*
venv/lib/python3.12/site-packages/p1
venv/lib/python3.12/site-packages/p1

所以a.py应该把p1p2当作顶层包来引用,比如这样:

from p1 import b
from p2 import c

而不是用相对导入的方式,就好像a是和p1p2在同一个包里一样。如果你选择了第二个选项:

from package.p1 import b
from package.p2 import c

如果你确实希望a.py成为一个可以作为脚本运行的模块,那么你的源代码树可能看起来像这样:

\project
    \package
        \p1
            b.py
        \p2
            c.py
        a.py

现在package.a是一个模块,连同package.p1package.p2,而a.py可以使用相对导入来引用p1p2。为了确保当a.py作为脚本执行时,相对导入也能正常工作,你需要在第一个相对导入之前添加以下代码:

if __name__ == '__main__' and __package__ is None:
    __package__ = 'package'

撰写回答