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/?