结论:import 的流程是:查 sys.modules 缓存 → 沿 sys.path 用 finder 定位模块 → 创建模块对象先放进缓存 → 执行模块代码填充内容。第二次导入直接命中缓存,这就是模块单例语义和"导入只执行一次"的来源。
展开:循环导入的成因正在于"先放缓存再执行":a.py 开头 import b,b.py 开头 import a,a 已在缓存但只执行了一半——from a import name 会立刻取尚未定义的 name 报 ImportError;而 import a 只取模块对象本身(已存在),只要不在模块顶层立即访问 a.name,运行时拿到完整模块后反而没事。解法按优先级:1)重构——把共同依赖抽到第三个模块,循环多由职责划分不清引起,这是治本;2)延迟导入——把 import 移进函数体内,执行时双方都已加载完毕;3)改为 import a 而非 from a import name,推迟名字绑定到调用时刻;4)类型注解场景的循环用 TYPE_CHECKING 块 + 字符串注解,运行时不导入。易错点:包内 __init__.py 里 re-export 子模块容易制造隐蔽循环;importlib.reload 不更新已导入的引用,调试时结果反直觉。
# a.py(方案:延迟导入)
def use_b():
from b import helper # 函数内导入,避开加载期循环
return helper()
追问方向:namespace package 与常规包的区别、相对导入为何要求"作为包的一部分运行"(python -m)。