上一篇我们知道了 Python 通过 sys.path 找模块。但 import 语句本身也有两种写法:绝对 import 和 相对 import,选错了同样会报错。
这篇讲清楚三件事:两种 import 的区别、怎么选、__init__.py 要不要加。
一、绝对 import:从根目录写完整路径
from utils.helpers import clean_data
这就是绝对 import。Python 会拿着 utils.helpers 这个路径,依次到 sys.path 的每个目录下去找 utils/helpers.py。
就像写文件的绝对路径一样,不管你当前在哪个目录,路径都从根开始写。这里的"根"就是 sys.path 里的某个目录。
my_project/ ← sys.path 中包含这个目录
├── core/
│ └── engine.py ← 在这里写 import
└── utils/
└── helpers.py ← 要 import 的目标
# core/engine.py
from utils.helpers import clean_data # 从 my_project/ 开始找
只要 my_project/ 在 sys.path 里,这个 import 就能成功。跟 engine.py 在哪个子目录无关。
二、相对 import:从当前文件位置出发
from ..utils.helpers import clean_data
开头的 . 表示"从我这个文件所在的位置出发":
-
.= 当前包(当前目录) -
..= 上一层包(父目录) -
...= 上两层包就像文件的相对路径,
../utils/helpers.py表示"先回到上一层,再进入 utils 目录"。
# core/engine.py
from .models import User # 同一个包内:core/models.py
from ..utils.helpers import clean_data # 上一层再进 utils:utils/helpers.py
三、相对 import 的限制
相对 import 有一个致命限制:只能在包内使用。
-
什么叫"包内"?满足两个条件:
- 文件所在目录有
__init__.py - 文件是被 import 的,或者通过
python -m运行的
- 文件所在目录有
-
如果你直接运行一个使用了相对 import 的文件:
python core/engine.py # ImportError: attempted relative import with no known parent package
这个报错的意思是:你把 engine.py 当作独立脚本运行了,Python 不知道它属于哪个包,.. 无法解析。
-
解决方法:用
python -m运行:cd my_project python -m core.engine # ✅ Python 知道 engine 属于 core 包
四、到底选哪种?
大多数情况下,用绝对 import。 理由很简单:
| 绝对 import | 相对 import | |
|---|---|---|
| 可读性 | 一眼看出目标模块在哪 | 要数 . 的个数,心算目录层级 |
| 移动文件后 | 不用改(只要包结构没变) | 可能要改(相对位置变了) |
| 直接运行 | 都不太行,但报错更直观 | 报错更让人困惑 |
| 推荐场景 | 默认选择 | 包内紧密耦合的模块之间 |
Python 官方风格指南(PEP 8)也推荐优先使用绝对 import。
相对 import 适合的场景:一个包内部的模块互相引用,并且你希望整个包被移动或重命名时不用改内部的 import。比如:
# core/engine.py
from .models import User # core 包内部引用,用相对 import 合理
from .config import settings # 同上
五、__init__.py:到底要不要加?
每个"包"目录下都建议放一个 __init__.py:
my_project/
├── core/
│ ├── __init__.py ← 这个文件
│ ├── engine.py
│ └── models.py
└── utils/
├── __init__.py ← 和这个文件
└── helpers.py
它可以是空文件,但建议加上。
5.1 不加会怎样?
Python 3.3+ 支持"命名空间包"(namespace packages),理论上不需要 __init__.py 也能 import。但实际开发中,不加会遇到这些问题:
- PyCharm 可能不识别这个目录为包,代码补全和跳转失效
- pytest 在某些配置下找不到测试模块
- 相对 import 会失败,Python 不确定这个目录是不是一个包
- 更隐蔽的问题:有些 import 方式会静默失败,不报错但 import 到的不是你想要的东西
5.2 建议增加
每个包目录都加一个 __init__.py,哪怕是空的。 零成本,省掉一类排查困难的问题。
# utils/__init__.py
# 空文件,仅标识这是一个 Python 包
如果你想在 import utils 时直接暴露一些常用接口,可以在 __init__.py 里写:
# utils/__init__.py
from .helpers import clean_data, format_output
这样外部可以直接 from utils import clean_data,不用写完整路径。
六、总结
- 绝对 import 是默认选择:路径清晰,不容易出错
- 相对 import 适合包内部:同一个包的模块之间互相引用时可以用
- 相对 import 只能在包内使用,直接
python file.py运行一定报错,要用python -m - 每个包目录加
__init__.py,空文件就行,能避免一堆隐晦问题
324



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



