你一定遇到过这个场景:项目里明明有个 utils.py,from utils import xxx 一运行就报 ModuleNotFoundError。换个目录运行,又好了。
这篇文章只讲一件事:Python 通过 sys.path 找模块的机制。搞懂它,80% 的 import 问题你都能自己解决。
一、先看一个报错
假设你的项目长这样:
my_project/
├── main.py
├── core/
│ ├── __init__.py
│ └── engine.py
└── utils/
├── __init__.py
└── helpers.py
engine.py 想用 helpers.py 里的函数:
# core/engine.py
from utils.helpers import clean_data
你在 core/ 目录下运行:
cd my_project/core
python engine.py
报错了:
ModuleNotFoundError: No module named 'utils'
但回到上一层:
cd my_project
python -m core.engine
正常运行。
为什么同样的代码,换个目录就坏了。 这不是 bug,要理解它,得先搞懂 sys.path。
二、sys.path 是什么?
当你写 import xxx 时,Python 不会搜遍整台电脑去找 xxx。它只在一组指定的目录里找,这组目录就是 sys.path。
你可以随时打印出来看:
import sys
for p in sys.path:
print(p)
输出大概长这样:
/home/you/my_project/core ← 第 1 项:跟你怎么运行脚本有关
/usr/lib/python3.11 ← 标准库
/usr/lib/python3.11/lib-dynload
/home/you/.local/lib/python3.11/site-packages ← 第三方包
Python 从第 1 项开始,挨个目录找,找到就加载,全部找不到就报 ModuleNotFoundError。所以 import 能不能成功,完全取决于目标模块在不在 sys.path 的某个目录下。
三、sys.path 里都有什么?
| 顺序 | 来源 | 说明 |
|---|---|---|
| 1 | 脚本所在目录 | 你运行的那个 .py 文件所在的目录 |
| 2 | PYTHONPATH 环境变量 | 你手动配置的额外搜索路径(如果设了的话) |
| 3 | 标准库目录 | Python 自带的模块,如 os、json |
| 4 | site-packages | pip install 装的第三方包 |
重点在第 1 项。 它是最容易变的,也是大多数 import 问题的根源。
四、关键问题:第 1 项到底是什么?
这取决于你怎么运行脚本。
-
先看结论:
python core/engine.py # sys.path[0] = core/(脚本所在目录) python -m core.engine # sys.path[0] = ./(当前工作目录) -
回到开头的例子。当你执行
python core/engine.py:sys.path[0]=my_project/core/- Python 在
core/目录下找utils,找不到,因为utils/和core/是平级的,不在core/里面
-
当你执行
python -m core.engine:sys.path[0]=my_project/(当前工作目录)- Python 在
my_project/下找utils——找到了
sys.path[0]不同,搜索起点就不同,结果就不同。
五、python xxx.py vs python -m xxx
这两种运行方式的区别是本文最重要的知识点:
python xxx.py | python -m xxx | |
|---|---|---|
sys.path[0] | 脚本所在目录 | 当前工作目录 |
| 适用场景 | 独立脚本(不依赖项目内其他模块) | 项目内的模块(需要互相 import) |
如果你的 .py 文件需要 import 项目里的其他模块,用 python -m 运行。
5.1 错误操作
cd my_project/core
python engine.py
5.2 正确操作
回到项目根目录,用-m:
cd my_project
python -m core.engine
注意 -m 后面写的是包路径(用 . 分隔),不是文件路径(不带 .py 后缀)。
六、那 sys.path.append 呢?
你可能见过这种"万能补丁":
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
它能跑,但不推荐:
-
脆弱:目录结构一改就要重新算路径
-
隐式:别人看你代码不知道为什么要加这行
-
扩散:每个需要跨目录 import 的文件都得加,项目越大越乱
临时调试用一下可以,但它是症状疗法,不是正解。正解是用
-m运行,或者后面两篇会讲的其他方案。
总结
这篇文章就讲了一件事:
Python 靠
sys.path找模块。你的模块找不到,就是sys.path里没有包含它所在的目录。
- 记住两条:
- 遇到
ModuleNotFoundError,先打印sys.path,看看你期望的目录在不在里面 - 项目内的模块用
python -m运行,不要直接python path/to/file.py
- 遇到
1万+



被折叠的 条评论
为什么被折叠?



