python执行脚本时导包错误如何解决

先放解决办法:

  1. 将未定位的包的上级目录加入到 sys.path 中。
  2. 使用 python -m xxx.yyy 的方式执行模块。

一、导包报错现象展示

假如我们有以下目录结构:

codes
│   main.py
│
├───db
│       service.py
│
└───utils
        excel.py

内容如下图所示,我们在 codes 目录下执行 python main.py 是可以正常运行的。

image

但是我们想直接执行 codes/db/service.py 脚本却报错了,如下图所示。
(在 codes 目录下执行报错,在 codes/db 目录下执行报错,使用vscode的调试模式执行也是报错。)

image

二、导包报错现象溯源

2.1 溯源过程

导包报错的提示很明确: ModuleNotFoundError: No module named 'utils' 也就是没找到这个包,如果要定位为什么没找到这个包,那我们就需要知道执行脚本时,会去哪里找我们 import 命令指定的包。

根据 python 官方文档 The import system 中所述,import 会去 sys.path 中检索我们要导入的包,如果没找到就会报导包错误。

那我们在脚本中添加输出 sys.path 的代码,再执行脚本,对比不同。

image

我们发现当我们执行 python xxx.py 的时候,python 会将脚本所在目录添加到 sys.path 中,如果执行的命令是 python xxxx/yyy.py,则添加到 sys.path 的值是 ./xxx

以上述代码为例,当我们直接执行 python main.py 的时候,python 将 codes 添加到了 sys.path 中,在这个目录中是可以找到我们 from utils import excel 中的 utils 包的,所以可以正常执行,而 python db/service.py 则是将 ./db 添加到了 sys.path 中,这个时候 sys.path 所有的目录下都没有 utils 这个包,所以报错。

2.2 解决方案

  1. 使用 python -m xxx/yyy.py 格式执行脚本(为这种情况量身定做。)

在 Python 中,-m 参数用于将模块作为脚本运行,其核心作用是

  1. 改变 Python 的导入行为,确保模块能以正确的上下文执行。
  2. 自动添加当前目录到 sys.path:确保模块能正确导入同级或父级包。
  3. 避免相对路径问题:解决直接运行 python xxx/yyy.py 时可能出现的导入错误。

image

  1. 在模块中手动更新 sys.path,将指定目录添加到其中。

image

三、Python 的导包机制

3.1 常规包和命名空间包

常规包是目录中包含 __init__.py 文件的包。
命名空间包是目录中不包含 __init__.py 文件的包。

python3.2(PEP0420)版本之前,无法直接导入非常规包,即目录中不包含 __init__.py 文件的时候,无法正常识别为 Python 包。
PEP 420 – Implicit Namespace Packages 定义了命名空间包,使得包中没有 __init__.py 文件时也可识别为包,并正常导入。所以常规包和命名空间包在根本上没有什么不同,只是识别导入的方式不同,而识别导入也是 Python 自动处理的,我们无需干涉,所以对我们来说,用起来没有区别。

3.2 导包逻辑

以导入 foo 包为例:

  1. 如果找到 /foo/__init__.py,则一个常规包被导入并返回。
  2. 如果没有,但是 /foo.{.py, .pyc, .so, .pyd} 被找到,则一个模块被导入并返回。
  3. 如果没有,但是 /foo 存在并且是一个目录,记录并继续扫描。
  4. 否则继续扫描。

如果扫描结束但是没有返回模块或包,并且记录了至少一个目录,则创建一个命名空间包。

3.3 常规包和命名空间包的细微区别

  1. 命名空间包和常规包功能上没有区别。
  2. 命名空间包下不能有 __init__.py,否则就会被识别成常规包。
  3. 命名空间包的 __path__ 属性是一个可迭代对象,里面记录着扫描过程中记录的目录信息,而常规包是一个普通列表。
  4. 命名空间包没有 __file__ 属性,而常规包次属性是指向 __init__.py 文件的值。
  5. 命名空间包的子模块可以来自不同路径,但是常规包只能在一个目录中。

3.4 命名空间包模块的多来源特性

Namespace 包(PEP 420)的核心用途是 允许一个逻辑上的包分散在多个物理目录,适用于模块化、插件化架构或代码分发的场景。
简单代码示例如下。

image

posted @ 2025-04-20 15:57  那个白熊  阅读(182)  评论(0)    收藏  举报