Python 如何控制模块之间的依赖方向?

Python通过分层设计、接口抽象、工具检测和局部导入等手段约束模块依赖方向,确保高层模块依赖低层模块,避免循环依赖并降低耦合度。

Python 本身不强制依赖方向,但可以通过设计规范和工具约束模块间的引用关系,避免循环依赖、降低耦合度。关键在于主动管理 import 语句的流向——让高层模块依赖低层模块,而非反过来。

明确分层,控制 import 方向

将代码按职责划分为清晰层级(如 core → service → api

models → utils → views),规定上层模块可以导入下层,下层不能反向导入上层。

  • 例如:api.py 可以 import service.pymodels.py,但 models.py 不能 import api.py
  • 用相对导入(from .. import xxx)时需格外注意包结构,避免跨层跳转
  • __init__.py 中谨慎暴露接口,避免隐式引入高阶模块

用接口抽象替代具体依赖

当两个模块需要协作但又不想形成强依赖时,可定义协议(Protocol)或抽象基类(ABC),让调用方依赖抽象,实现方负责具体注入。

  • 比如定义 class DataFetcher(Protocol): def fetch(self) -> dict: ...
  • processor.py 只依赖该协议,不关心 database_fetcher.pyhttp_fetcher.py 的存在
  • 运行时通过参数或配置传入具体实现,解耦编译期依赖

借助工具检测和阻断违规依赖

静态检查能提前发现不符合约定的 import 关系。

  • 使用 pydeps 生成依赖图: pydeps mypackage --max-bacon=2 查看实际引用链
  • archunit-py(或类似规则引擎)编写策略,例如“禁止 app.api 导入 app.tasks
  • 在 CI 中集成 pylint --enable=import-error,import-self 等检查项,失败即拦截

延迟导入与局部 import 控制作用域

把 import 放在函数或方法内部,可避免模块级循环引用,并限制依赖生效时机。

  • 适合只在特定路径下才需要的模块(如某些 CLI 命令仅在运行时用到数据库)
  • 例如:def run_migration(): import alembic; alembic.command.upgrade(...)
  • 注意:不能解决根本的设计问题,仅作为补充手段,过度使用会增加维护成本