Python相对导入:ModuleNotFoundError:找不到模块名为'sib1'

4

我有一个类似如下的项目层次结构,当我运行 python src/bot/main 时,没有出现错误。但是,如果我运行 python -m src.bot.main,就会出现错误。为什么?

这是我的文件层次结构:

MyProject
└── src
    ├── __init__.py
    ├── bot
    │   ├── __init__.py
    │   ├── main.py
    │   └── sib1.py
    └── mod
        ├── __init__.py
        └── module1.py

这是main.py的内容:
import sys
    

if __name__ == "__main__":

    # sys.path will print the parent folder.
    print(sys.path, end="\n\n")
    
    # my problem is on this line.
    import sib1
    sib1.test()
    

错误:

Traceback (most recent call last):
  File "/usr/local/Caskroom/miniconda/base/envs/test/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/Caskroom/miniconda/base/envs/test/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/Users/me/Desktop/test_py/src/bot/main.py", line 16, in <module>
    import sib1
ModuleNotFoundError: No module named 'sib1'

目前我得出了一些结论:

因为在两种情况下sys.path的输出都包括/Users/me/Desktop/MyProject,所以原因不应该与作用域相关吧?


python -m src.bot.mainpython src/bot/mainsys.path输出:

(test) ✔ me Desktop/test_py % python -m src.bot.main
['/Users/me/Desktop/test_py', '/usr/local/Caskroom/miniconda/base/envs/test/lib/python39.zip', '/usr/local/Caskroom/miniconda/base/envs/test/lib/python3.9', '/usr/local/Caskroom/miniconda/base/envs/test/lib/python3.9/lib-dynload', '/usr/local/Caskroom/miniconda/base/envs/test/lib/python3.9/site-packages']

1
使用 python -m src.bot.main 命令告诉 Python,src 是一个顶级包。在目录结构中,src 下面的所有内容都将被视为 src 的子模块/子包。在该组织下,sib1 的正确名称是 src.bot.sib1。就 Python 而言,不存在名为 sib1 的顶级模块。 - Brian61354270
1
如果您希望Python能够了解包结构,那么使用python -m是必需的。如果您只需要运行子模块进行测试,可以考虑使用更强大的测试工具,例如doctest(轻量级)或unittest(重量级)。 - Brian61354270
1
你可能还想选择一个比 src 更具信息性的包名。将你的包文件放置在 MyProject/src/MyProject 或者仅仅是 MyProject/MyProject 下是很典型的做法。只要确保包含 MyProject(即包)的目录在你的 Python 路径上即可。然后你就可以使用 import MyProject.bot.sib1 等语句了。 - Brian61354270
1
此外,您可以将 main.py 重命名为 __main__.py,以使包直接可执行。例如,python -m MyProject.bot 将执行 MyProject.bot.__main__ - Brian61354270
1
回复:Python路径问题:通过python -m运行的包需要在sys.path中的一个目录中。否则,Python将无法找到它。您当前的工作目录(例如您的示例中的〜/ Desktop / test_py)始终位于您的Python路径上,因此如果您在包含MyProject的目录中,则使用python -m MyProject.x.y.z将起作用。如果您在任何其他目录中,则需要手动将包目录添加到Python路径中。许多IDE允许您在运行配置设置中执行此操作。或者,您可以设置环境变量PYTHONPATH - Brian61354270
显示剩余4条评论
1个回答

3

我会尽力以问答形式澄清我的每一个疑惑,并在此过程中整理@Brain的评论:


问题1:当我运行python src/bot/main时,没有错误。

sys.path将包括包含文件main.py当前目录,即解释器将看到文件MyProject/src/bot

import sib1

逻辑上等同于:

import "MyProject/src/bot" + "/sib1.py"

因此,没有错误。

Q2: 如果我运行python -m src.bot.main,为什么会出错?

现在是引用@Brain宝贵(第一个)评论的时候了:

使用python -m src.bot.main告诉Pythonsrc是顶级包。目录结构中src以下的所有内容都将被视为src的子模块/子包。根据这种组织方式,sib1的正确名称是src.bot.sib1。就Python而言,不存在名为sib1的顶级模块。

(由我强调)

所以:

  • -m选项将为您要运行的文件定义范围(因此是顶级包)。
  • 所有不以src.开头的包都将被视为安装在虚拟环境中的内置第三方库。由于我没有安装名为sib1的包,所以你得到了错误。(在这种情况下有意忽略了sys.path,因为从Python 3.3开始没有隐式相对导入)
  • 所有相对导入都不应超出顶级包src(通过在import中添加太多个.)。

例如,这将起作用:

from . import sib1
from ..mod import module1    # The `..` is equivalent to the MyProject/src.

module1.hello()
sib1.test()

最后,不要通过插入许多 if __name__ == '__main__' 来测试您的软件包。请使用专业工具进行测试:

如果您只需要运行子模块以进行测试,请考虑使用更强大的测试工具,如 doctest(轻量级)或 unittest(重量级)。

这是一篇不错的文章,我几年前为它投了300个赏金,你一定要找时间读一读。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接