大家好,我是正在实战各种 AI 项目的程序员晚枫。
import 语句背后发生了什么?模块缓存、导入钩子、相对导入...这一讲,揭开 Python 导入系统的全部秘密。
📖 开篇:import 比你想象的复杂
1 2 3 4
| import os from collections import defaultdict import numpy as np from . import utils
|
这些 import 语句背后,Python 做了大量工作:
- 查找模块(sys.path)
- 加载模块代码
- 缓存模块对象(sys.modules)
- 绑定到命名空间
🔍 完整导入流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import os ↓ Step 1: 检查 sys.modules sys.modules 中已有 os? ↓ 是 直接返回缓存的模块对象 ↓ 否 ↓ Step 2: 查找模块(Finder 链) sys.meta_path 中的每个 Finder 尝试 find_module() ↓ Step 3: 加载模块(Loader) Finder 返回 Loader,调用 load_module() ↓ Step 4: 执行模块代码 创建模块对象 -> 执行模块代码 -> 设置 __name__ 等属性 ↓ Step 5: 缓存 加入 sys.modules ↓ Step 6: 绑定 在当前命名空间中创建变量 os 指向模块对象
|
sys.modules 详解
1 2 3 4 5 6 7 8
| import sys
print(len(sys.modules)) print('os' in sys.modules)
|
sys.path(模块查找路径)
1 2 3 4 5 6 7 8 9
| import sys for i, path in enumerate(sys.path): print(f'{i}: {path}')
|
🎯 Finder 与 Loader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import sys
for finder in sys.meta_path: print(finder)
class DebugFinder: def find_module(self, fullname, path=None): print(f'尝试导入: {fullname}') return None
sys.meta_path.insert(0, DebugFinder())
import json
|
自定义 Loader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import sys import importlib.abc import importlib.machinery
class MyLoader(importlib.abc.Loader): def create_module(self, spec): return None
def exec_module(self, module): module.my_var = 'Hello from MyLoader!' module.my_func = lambda: 'func called'
spec = importlib.machinery.ModuleSpec( 'my_module', MyLoader(), origin='custom' ) sys.modules['my_module'] = importlib.util.module_from_spec(spec) spec.loader.exec_module(sys.modules['my_module'])
import my_module print(my_module.my_var)
|
📦 包(Package)的导入
init.py
1 2 3 4 5 6 7 8 9 10
| print('mypackage 正在初始化...')
from . import utils from .core import process
__all__ = ['utils', 'process', 'init']
def init(): print('mypackage initialized')
|
1 2 3 4
| import mypackage
mypackage.init()
|
相对导入
1 2 3 4
| from . import helpers
|
main.py(可执行包)
1 2 3 4 5
| from .core import main
if __name__ == '__main__': main()
|
🔄 重新加载模块
1 2 3 4 5 6 7 8 9 10 11 12 13
| import importlib
import json import json
importlib.reload(json)
import mymodule
importlib.reload(mymodule)
|
⚠️ 常见陷阱
陷阱1:循环导入
1 2 3 4 5 6 7 8 9 10 11
| from b import B class A: pass
from a import A class B: pass
import a
|
解决:延迟导入(在函数内部 import)、重构模块结构。
陷阱2:导入顺序依赖
💡 本节作业
- 打印 sys.path 并解释每条路径的作用
- 写一个 DebugFinder 拦截所有 import 请求
- 创建自己的包,包含 init.py、utils.py,验证导入行为
🎯 本讲总结
导入流程:检查缓存 -> Finder 查找 -> Loader 执行 -> 缓存模块 -> 绑定命名空间。
sys.modules:模块缓存字典,所有已导入模块都在这里。
sys.path:模块查找路径列表,按顺序搜索。
Finder/Loader:元路径查找器和加载器,可以自定义导入行为。
包的结构:init.py、main.py、相对导入。
📚 推荐教材
《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》
🔗 课程导航
← 上一讲:线程与并发 | 下一讲:C 扩展编程 →
💬 联系我
主营业务:AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!