合理模块粒度应以责任边界清晰、import稳定、避免循环依赖和隐式耦合为标准,如auth.py专注凭证与会话,不掺杂邮件发送;禁用utils.py式黑洞;子包用于共享约定或封装实现细节,__init__.py仅声明包并控制导出,不执行业务逻辑;松耦合体现为依赖单向、可独立mock、变更影响可控。

模块拆分到什么粒度才算合理
模块不是越小越好,也不是按功能名硬切。关键看 import 是否稳定、from ... import 是否频繁引发循环依赖、测试时是否必须连带加载一堆无关逻辑。
实操建议:
- 一个模块对应一个明确的「责任边界」,比如
auth.py处理凭证校验和会话管理,但不包含密码重置邮件发送(那是notifications.py的事) - 如果两个函数总是一起被调用、参数高度耦合、修改一个必改另一个,它们大概率不该拆到不同模块
- 避免出现
utils.py这种“啥都往里塞”的模块——它迟早变成隐式依赖黑洞,改一行可能影响三个服务
什么时候该用子包而不是平级模块
当一组模块开始共享内部约定(如共用 _base.py、共用 __all__ 导出规则)、或需要隐藏实现细节(比如把 _parser.py 和 _serializer.py 放进 serializers/ 包,只暴露 serialize() 和 deserialize() 接口)。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
- 把
models/做成包,但里面全是独立的user.py、order.py,没定义__init__.py导出逻辑,结果外部还得写from models.user import User—— 这和扁平模块没区别 - 子包里放了太多顶层接口函数,导致使用者搞不清该导入
pkg.foo()还是pkg.submod.bar() - 子包之间存在跨包的
from .. import相对导入,一旦目录结构调整,ImportError: attempted relative import with no known parent package立刻报错
__init__.py 里该写什么、不该写什么
它的核心作用是声明“这里是个包”,顺便控制对外暴露的接口。不是初始化脚本,也不该承担业务逻辑。
实操建议:
- 必须有(哪怕空文件),否则 Python 3.3+ 可能忽略该目录为包(尤其在
zipimport或某些打包场景下) - 推荐显式定义
__all__ = ["Client", "connect"],防止from pkg import *污染命名空间 - 禁止在其中调用耗时操作(如读配置、连数据库),因为只要有人
import pkg就会触发——而这个 import 可能发生在 CLI 启动阶段,拖慢整个命令响应 - 避免在
__init__.py里做条件导入(如if sys.version_info > (3, 8): from typing import Literal),应统一移到具体模块中按需处理
模块间依赖怎么才算“松”
松耦合不是指“没有 import”,而是指依赖方向清晰、变更影响可控。比如 api.py 依赖 service.py 是合理的,但反过来就不行;service.py 依赖 db.py 可以,但如果它还直接 import 了 cache.py 和 metrics.py,就说明职责过重。
判断依据:
- 运行
python -m pydeps mypkg --max-bacon=1(需装pydeps),看依赖图是否出现双向箭头或密集星型结构 - 尝试 mock 一个模块的依赖:如果为了测
payment.py,你得 patchlogging、requests、settings、user.py四个东西,那它大概率太紧了 - 修改
models.py中一个字段类型,结果api.py、admin.py、migrations/全报错——说明模型被到处硬引用,该考虑加一层 repository 或 DTO 隔离
git blame 时,你能快速定位到“谁该对这个 bug 负责”,而不是翻十来个文件才搞懂数据从哪来、到哪去。










