Prompts-MCP 686 skills · ai / design / design-pattern / framework / fundamentals / habit / lang / tech-selection /mcp/sse
lang lang/python/import/no-circular-import.md medium

python-no-circular-import

Python Service 层禁循环依赖 — 不互相 import,必要时下移共享逻辑到 helper。Use when 写 Python 后端代码 / 评审涉及 `no-circular-import` 的 PR。

循环依赖circular importimport order模块依赖
paths
  • backend/services/**/*.py
  • **/services/**/*.py

Python · 禁循环依赖

规则

任何两个 Service / Adapter / Agent 之间不能互相 import。Repository / Model 也不允许向上调 Service。

合法依赖方向(仅向下):

Router  Service  Adapter  Schema/Model
            
           Agent  Tools
            
           Core (config / db / redis / logger / exceptions)

反例

# services/tenant_manager.py
from services.credits_service import deduct_credits

# services/credits_service.py
from services.tenant_manager import get_current_tenant   # ← 循环!

报错形式:ImportError: cannot import name 'X' from partially initialized module 'Y'

拆解模式

模式 1:抽出共享下层

把两个 Service 都需要的逻辑下沉到 core/ 或新增 services/_shared/

# services/_shared/auth_tenant.py
async def get_active_tenant(tenant_id: str) -> Tenant: ...

# services/tenant_manager.py
from services._shared.auth_tenant import get_active_tenant

# services/credits_service.py
from services._shared.auth_tenant import get_active_tenant

模式 2:依赖注入

被调用的 Service 作为参数传入,而不是 import:

# services/credits_service.py
async def deduct(uid: int, amount: int, tenant_loader):
    tenant = await tenant_loader(uid)
    ...

# services/tenant_manager.py
from services.credits_service import deduct
await deduct(uid, 1, tenant_loader=load_tenant_internal)

模式 3:事件 / 信号

完全解耦:发事件,让另一边订阅。在规模未到事件总线时可暂缓。

工具

# 用 pydeps 找循环
uv run pydeps backend --max-bacon 0 --reverse > /tmp/deps.svg

或用 ruff 的 flake8-import-cycle 规则(如启用)。

自检

  • [ ] 两个 Service 不互相 import?
  • [ ] Repo / Model 不 import Service?
  • [ ] 共享下层放 core/services/_shared/

相关