模块拆分的关键在于合理界定边界:需避免循环导入、职责模糊、接口不稳、测试失焦,应按行为而非技术类型划分,抽取共享基础,明确输入输出边界,并确保核心逻辑可纯函数化测试。

模块拆分本身不是问题,问题常出在“怎么拆”和“为什么这么拆”。很多团队把代码按文件大小或功能粗略切开,结果反而让依赖变乱、测试变难、协作变卡。
循环导入:最隐蔽也最致命
两个模块互相 from A import X 和 from B import Y,Python 在加载时直接报错。这不是语法错误,是运行时才暴露的结构缺陷。
- 典型场景:models.py 里定义 User 类,同时需要校验逻辑;validators.py 又要导入 User 做类型检查
- 解决思路:把共享基础(如数据结构、常量、抽象基类)抽到单独的 common 或 types 模块,让其他模块只单向依赖它
- 避免用 if TYPE_CHECKING: 临时绕过,那只是掩盖问题,不是解耦
职责模糊:一个模块干了三件事
比如叫 red">utils.py 的文件,里面混着日期格式化、HTTP 请求封装、数据库连接池配置——名字没说清它该做什么,改一处可能影响八处。
- 判断标准:如果模块名不能用“动词+名词”一句话说清它的唯一职责(如 send_email、parse_csv),就该拆
- 推荐做法:按行为边界划分,而不是按技术类型。比如 “用户注册流程” 相关逻辑,哪怕涉及 DB、Redis、发邮件,也可先放在 registration/ 包里,再按层(service、adapter、dto)细化
- 别迷信“小就是好”:5 行的模块如果只是为隔离一个硬编码字符串,不如直接内联;200 行的模块如果逻辑高度内聚,也没必要硬拆
接口不稳:内部实现直接暴露成公共 API
拆分后本想提高复用性,结果别人开始 import 你模块里的私有函数甚至类属性,下次你重构 rename 就全崩了。
立即学习“Python免费学习笔记(深入)”;
- Python 没真正的 private,但可以用 _ 前缀明确标记非公开接口,并在 __all__ 里只声明真正支持的符号
- 对外暴露的函数/类,参数和返回值尽量用简单类型(str、int、dict、dataclass),少用模块内部自定义的复杂类——否则调用方等于被迫依赖你的内部结构
- 如果发现多个模块都在重复写类似逻辑(比如都手动拼 SQL),说明缺的是一个稳定的抽象层,不是更多碎片文件
测试失焦:拆完反而更难测
原本一个函数里完成输入→处理→输出,现在变成 A 调 B、B 调 C、C 又回调 A,单元测试得 mock 四层,集成测试又因路径太深跑不起来。
- 关键原则:每个模块应有清晰的“输入边界”和“输出边界”,比如接收 dict、返回 dict;或者接收 Request、返回 Response
- 拆分后优先保证 核心逻辑可纯函数化:把 IO、时间、随机等副作用抽到上层 adapter,让中间层专注计算——这样测试只需给输入、断言输出
- 警惕“为拆而拆”的包结构:像 core/ → domain/ → application/ → infrastructure/ 这种分法,若没有对应团队共识和演进节奏,只会增加理解成本










