__import__ 默认只返回顶层包而非目标子模块,应优先使用 importlib.import_module 进行动态导入,它支持准确的相对导入、清晰的层级语义和更好的错误提示。

__import__ 会跳过部分包层级解析
当你用 __import__ 加载形如 "package.submodule" 的路径时,它默认只返回最顶层的包(即 package),而不是你写的完整路径所指的模块。这是历史遗留行为,源于早期 Python 的 import 机制设计——__import__ 本质是 import 语句的底层实现,不是为直接调用设计的。
实操建议:
- 若必须用
__import__,需手动遍历__path__或用getattr逐级取子模块,比如getattr(__import__("a.b.c"), "b").c - 对动态导入路径,优先用
importlib.import_module("a.b.c"),它严格按字符串路径解析并返回目标模块 - 注意
__import__的fromlist参数:传["*"]或非空列表可强制返回最后一级模块,但行为易混淆,不推荐依赖
importlib.import_module 支持相对导入上下文
importlib.import_module 可以接受 package 参数,从而正确处理相对导入(如 "..utils")。而 __import__ 没有等价机制,传入相对路径会直接报 ValueError: attempted relative import with no known parent package。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
- 在脚本中直接调用
__import__("..utils", fromlist=["something"])→ 报错 - 用
importlib.import_module("..utils", package="myapp.api")→ 正常工作 - 省略
package参数时,importlib.import_module也退化为绝对导入,和__import__表现趋同
__import__ 的 level 参数含义容易误解
__import__ 第四个参数 level 控制相对导入深度,但它的值是「相对于当前模块的父级层数」,不是「向上跳几级」。例如,在 myapp.api.v1 中想导入同级的 utils,应设 level=1(回到 myapp.api),再通过 fromlist 获取 utils —— 这比写清楚路径还绕。
对比之下:
-
importlib.import_module(".utils", package="myapp.api.v1"):点号数量直观对应层级,语义清晰 -
__import__的level在交互式环境或主模块中常为 0,导致相对导入完全失效 - 很多开发者误以为
level=2就是跳两级,实际它只影响解析起点,不自动拼路径
异常堆栈和模块缓存行为不同
两者都走 Python 的模块缓存(sys.modules),但出错时的提示信息差异明显:__import__ 报错常指向内部 C 层,比如 ImportError: No module named 'xxx';而 importlib.import_module 的异常更贴近用户写的路径,且会包含 name 和 package 字段,便于调试。
性能与兼容性提醒:
- 两者底层都查
sys.modules,首次导入开销基本一致 -
importlib.import_module是 Python 3.4+ 官方推荐方式,__import__在文档中标记为「for interpreter use only」 - 某些工具(如 PyInstaller、mypy)对
__import__的静态分析支持较弱,可能漏掉依赖
真正麻烦的是嵌套动态导入场景:比如从字符串拼接路径再导入,这时 __import__ 的层级逻辑和 fromlist 组合极易出错,而 importlib.import_module 只要字符串对就稳。别为了省一个 import importlib 去碰 __import__。


















