一个稍大的 Python 项目,import 语句几乎无处不在。但到底是写 from mypkg.sub import func,还是写 from .sub import func?两种风格各有拥护者,混用更是常见。搞清楚它们的语法边界和行为差异,能帮你避开不少"明明本地跑得好、一打包就炸"的坑。
绝对导入:从包根出发
绝对导入用项目的顶层包名作为起点,路径完整、不依赖当前文件位置:
# mypkg/utils/math.py 中导入同包另一模块
from mypkg.utils.strings import slugify
# 导入标准库或第三方包——天然就是绝对导入
import os
from collections import defaultdict
PEP 8 明确推荐绝对导入作为默认风格,理由很直接——路径一眼可读,IDE 跟踪跳转方便,模块挪动后只要包名不变,导入语句不用改。
但绝对导入也有一个实际痛点:顶层包名很长时,from mycompany.myproject.core.validators import check_email 这种语句既冗长又脆弱——项目一旦改名,每一行都得跟着改。
相对导入:以当前模块为锚点
相对导入用 . 表示当前包、.. 表示上一级包,以此类推:
# mypkg/utils/math.py
from .strings import slugify # 同级 mypkg/utils/strings.py
from ..models import User # 上级 mypkg/models.py
from ...config import SETTINGS # 再上一级(三层相对导入,少见但合法)
相对导入的优势是短且与包名解耦——不管顶层包叫 mypkg 还是 mycompany_mypkg,.strings 始终指向同目录下的兄弟模块。重构包名时,相对导入语句零改动。
代价同样明显:读代码时必须先知道当前文件在哪个层级,才能脑补 ..models 指向哪里;超过两层(...)的相对导入可读性急剧下降,PEP 8 建议避免。
一个可运行的项目示例
下面用一个最小项目演示两种导入的实际行为。先创建目录结构:
mkdir -p mypkg/utils mypkg/models
touch mypkg/__init__.py mypkg/utils/__init__.py mypkg/models/__init__.py
然后写入各模块内容:
# mypkg/config.py
SETTINGS = {"debug": True}
# mypkg/models/user.py
from ..config import SETTINGS # 相对导入:从上级包 mypkg 导入
class User:
def __init__(self, name):
self.name = name
self.debug = SETTINGS["debug"]
def __repr__(self):
return f"User({self.name}, debug={self.debug})"
# mypkg/utils/strings.py
def slugify(text):
return text.strip().lower().replace(" ", "-")
# mypkg/utils/math.py
from mypkg.utils.strings import slugify # 绝对导入:从包根写完整路径
def safe_label(raw):
return slugify(raw)
顶层入口文件放在项目根目录(与 mypkg/ 同级):
# main.py
from mypkg.models.user import User # 绝对导入
from mypkg.utils.math import safe_label # 绝对导入
u = User("Alice")
print(u)
print(safe_label("Hello World"))
运行:
python main.py
# 输出:
# User(Alice, debug=True)
# hello-world
注意:不要在
mypkg目录内直接执行python utils/math.py——Python 会把utils/math.py当作顶层脚本,此时__package__为None,相对导入会抛出ImportError: attempted relative import with no known parent package。这是相对导入最常见的踩坑点。
什么时候该选哪种
| 场景 | 推荐风格 | 原因 |
|---|---|---|
| 导入标准库 / 第三方包 | 绝对导入 | 没有相对路径可选 |
| 同一子包内兄弟模块互导 | 相对导入(单层 .) |
简短、与包名解耦 |
| 跨子包导入(如 utils → models) | 绝对导入 | 路径清晰,避免多层 .. |
| 包可能被改名 / 重组 | 相对导入(内部互导) | 重构时改动最少 |
| 编写可独立运行的脚本 | 绝对导入 | 避免 no known parent package 错误 |
一个务实策略:包内部用单层相对导入,跨包用绝对导入。这样既享受了短路径的好处,又不会让 .. 和 ... 满天飞。
检查清单
下次写 import 时,快速过一遍这几条:
- ✅ 相对导入是否只用了单层
.?超过两层就该换成绝对导入。 - ✅ 有没有在包内直接
python some_module.py跑测试?改用python -m mypkg.utils.math或写独立测试脚本。 - ✅ 绝对导入的顶层包名是否和
setup.py/pyproject.toml里的包名一致?不一致打包后必炸。 - ✅
__init__.py是否缺失?Python 3.3+ 支持 namespace package,但如果你用相对导入,每个中间目录仍需要__init__.py。 - ✅ IDE 能否正确跳转?绝对导入几乎不会出问题;相对导入在某些 IDE 里跳转依赖
__init__.py的存在。
搞清这两条路线的边界,剩下的就是根据项目规模和团队习惯做取舍——不必追求统一,但要在每个 import 行前想清楚"为什么这样写"。