Python 绝对导入与相对导入:写法、陷阱与选择

2026-05-19 15 预计阅读时间:1 分钟
来源:realpython.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:6 分钟

一个稍大的 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 时,快速过一遍这几条:

  1. ✅ 相对导入是否只用了单层 .?超过两层就该换成绝对导入。
  2. ✅ 有没有在包内直接 python some_module.py 跑测试?改用 python -m mypkg.utils.math 或写独立测试脚本。
  3. ✅ 绝对导入的顶层包名是否和 setup.py / pyproject.toml 里的包名一致?不一致打包后必炸。
  4. __init__.py 是否缺失?Python 3.3+ 支持 namespace package,但如果你用相对导入,每个中间目录仍需要 __init__.py
  5. ✅ IDE 能否正确跳转?绝对导入几乎不会出问题;相对导入在某些 IDE 里跳转依赖 __init__.py 的存在。

搞清这两条路线的边界,剩下的就是根据项目规模和团队习惯做取舍——不必追求统一,但要在每个 import 行前想清楚"为什么这样写"。


相关推荐