Python 如何正确拆分大型项目包结构?

核心是按业务域而非技术层划分顶层包,如orders/、products/、payments/,各包内聚自身代码并禁止跨域直接导入,依赖通过接口或抽象层单向流动,用__init__.py控制对外API,共享逻辑应独立成包或设shared/目录,辅以自动化检查保障结构健康。

拆分大型 Python 项目包结构,核心是让模块职责清晰、依赖可控、测试可隔离、团队协作顺畅。不是简单按功能建文件夹,而是围绕“可维护性”和“可演进性”设计边界。

按业务域(Domain)而非技术层划分顶层包

避免常见误区:把所有 models 放 models/、所有 views 放 views/——这会导致跨域耦合、迁移困难、领域逻辑被稀释。正确做法是每个核心业务域自成一个子包,内聚其全部相关代码。

  • 例如电商项目:划分为 orders/products/payments/,每个包含自己的 models.pyservices.pyapi.pytests/,甚至独立的 pyproject.toml(如需单独打包或配置)
  • 关键约束:包之间禁止直接导入对方的内部模块(如 orders.models 不应被 products.services 导入),通信通过明确定义的接口(如协议类、DTO、事件总线)或公共抽象层

明确分层与依赖方向,用 __init__.py 控制对外接口

每个子包内部可进一步分层(如 domain/application/infrastructure/),但必须保证依赖单向流动:上层可依赖下层,下层绝不可反向依赖上层。同时,用 __init__.py 显式导出稳定 API,隐藏实现细节。

  • 示例orders/__init__.py 只写 from .application import place_order, cancel_order,不暴露 .infrastructure.db.domain.entities
  • 好处:外部代码只看到契约,内部重构(比如换数据库驱动)不影响调用方;静态检查工具(如 mypy、pylint)也能更好识别非法引用

提取共享逻辑为独立包,避免隐式复用

当多个业务包出现相似工具函数、通用模型或中间件时,不要复制粘贴,也不要塞进一个模糊的 utils/ 包里。应评估其是否具备独立演进能力——如果它有自己版本号、测试套件、文档,并可能被其他项目复用,就拆成真正的第三方风格包(本地或私有 PyPI)。

  • 判断标准:该逻辑是否与任何具

    体业务域解耦?是否有清晰不变的输入/输出契约?是否需要独立 CI 和发布流程?
  • 轻量替代方案:若尚未达到独立包成熟度,可用 shared/ 目录 + pyproject.toml 中配置 [tool.setuptools.packages.find] 排除它,确保主项目安装时不包含,仅作开发期依赖

自动化验证结构健康度

再好的设计也会随时间退化。加入轻量级检查机制,把架构约束变成可执行规则。

  • 禁止跨域导入:用 pydeps 或自定义 ast 脚本扫描,报错提示 products.services 不得导入 orders.models
  • 检查 public API 稳定性:用 pycln 清理未导出的符号;配合 pyrightreportUnusedImport 防止 __init__.py 暴露多余内容
  • CI 中固化:将这些检查作为 pre-commit hook 和 CI step,失败即阻断合并

不复杂但容易忽略:结构设计不是一次性任务,而是在每次新增功能、重构模块、引入新依赖时持续校验和微调的过程。真正健康的包结构,是让人在阅读 import 语句时,就能自然理解系统边界与协作关系。