Python中如何解决循环导入的问题?小白求教
最近刚开始学 python 和 fastapi, 按照 fastapi 教程学习时遇到个问题, 因为使用 sqlmodel 的 relationship, 导致两个模型文件相互导入, 然后报错了, 根据官方文档的解决办法, 使用 TYPE_CHECKING 和字符串版本类型后暂时解决了报错.
但是当接口通过 response_model 指定了数据模型(非表模型)作为返回类型时, 又出现了报错, 报错提示如下:
`TypeAdapter[typing.Annotated[app.models.teams.TeamPublicWithUser, FieldInfo(annotation=TeamPublicWithUser, required=True)]]` is not fully defined; you should define `typing.Annotated[app.models.teams.TeamPublicWithUser, FieldInfo(annotation=TeamPublicWithUser, required=True)]` and all referenced types, then call `.rebuild()` on the instance.
求各位大佬帮忙看看, 哪里有问题.
python 版本用的是 3.12, 具体代码如下
models/users.py
from typing import TYPE_CHECKING, Any, Optional
from sqlmodel import Field, Relationship, SQLModel
if TYPE_CHECKING:
from .teams import Team, TeamPublic
class UserBase(SQLModel):
username: str = Field(index=True, max_length=255, unique=True)
email: str | None = Field(default=None, index=True, max_length=255)
is_active: bool = True
is_superuser: bool = False
full_name: str | None = Field(default=None, max_length=255)
team_id: int | None = Field(default=None, foreign_key=“team.id”)
class User(UserBase, table=True):
id: int | None = Field(default=None, primary_key=True)
hashed_password: str
team: Optional[“Team”] = Relationship(back_populates=“members”)
class UserPublic(UserBase):
id: int
class UserPublicWithTeam(UserPublic):
team: Optional[“TeamPublic”] = None
models/teams.py
from typing import TYPE_CHECKING, Any, List
from sqlmodel import Field, Relationship, SQLModel
if TYPE_CHECKING:
from .users import User, UserPublic
class TeamBase(SQLModel):
name: str = Field(max_length=255)
class Team(TeamBase, table=True):
id: int | None = Field(default=None, primary_key=True)
members: List[“User”] = Relationship(back_populates=“team”)
class TeamPublic(TeamBase):
id: int
class TeamPublicWithUser(TeamPublic):
members: List[“UserPublic”] = []
router/users.py
# 用户相关接口
from sqlmodel import and_, select
from app.api.deps import SessionDep
from app.models.users import (
User,
UserPublicWithTeam,
)
from app.new_router import new_router
router = new_router(prefix="/users", tags=[“用户管理”])
@router.get("/{id}", summary=“获取用户详情”, response_model=UserPublicWithTeam)
async def get_user_api(db: SessionDep, id: int):
user = db.exec(select(User).where(and_(User.id == id, User.is_active))).first()
return user
router/teams.py
# 团队相关接口
from sqlmodel import select
from app.api.deps import SessionDep
from app.models.teams import (
Team,
TeamPublicWithUser,
)
from app.new_router import new_router
router = new_router(prefix="/teams", tags=[“团队管理”])
@router.get("/{id}", summary=“获取团队详情”, response_model=TeamPublicWithUser)
async def get_team_api(db: SessionDep, id: int):
team = db.exec(select(Team).where(Team.id == id)).first()
return team
Python中如何解决循环导入的问题?小白求教
.
├── db.py
├── main.py
├── models
│ ├── init.py
│ ├── pycache
│ │ ├── init.cpython-311.pyc
│ │ ├── teams.cpython-311.pyc
│ │ └── users.cpython-311.pyc
│ ├── teams.py
│ └── users.py
├── pycache
│ ├── db.cpython-311.pyc
│ └── main.cpython-311.pyc
├── pyproject.toml
├── README.md
├── router
│ ├── init.py
│ ├── pycache
│ │ ├── init.cpython-311.pyc
│ │ ├── teams.cpython-311.pyc
│ │ └── users.cpython-311.pyc
│ ├── teams.py
│ └── users.py
├── test.db
└── uv.lock
db.py
from fastapi import Depends
from sqlmodel import Session
from typing import Generator
from sqlmodel import SQLModel, create_engine
DATABASE_URL = “sqlite:///./test.db"
engine = create_engine(DATABASE_URL, echo=True)
def get_session() -> Generator[Session, None, None]:
with Session(engine) as session:
yield session
SessionDep = Depends(get_session)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
teams.py
from sqlmodel import select
from sqlmodel import Session
from db import SessionDep
from models.teams import (
Team,
TeamPublicWithUser,
)
from fastapi import APIRouter
router = APIRouter(prefix=”/teams", tags=[“团队管理”])
.get("/{id}", summary=“获取团队详情”, response_model=TeamPublicWithUser)
async def get_team_api(id: int, db: Session = SessionDep, ):
team = db.exec(select(Team).where(Team.id == id)).first()
return team
我 get 到问题了是 models/teams.py 和 models/users.py 相互引用
我觉得可以从两个方面入手
1. 需求上 users.py 里的表是否可以不依赖 teams
2. 存在外键的表独立出来 users_teams.py?
把循环引用的 import 放到 class 里面,不要放到文件开头
使用依赖注入设计模式
X-Y 问题。
从建模上来说,Team 是由 User 构成,Team 引用 User 是正确的,但 User 为什么要引用 Team ?
这明显是个建模错误,而非循环导入问题。
这是 sqlmodel 的官方示例, 模型就是这么建的
这说明官方的模型有错误。你自己换个模型试试看。
问题出在 UserPublicWithTeam 这个 model 上,考虑把它移出去,移到 models/teams.py 里,或者独立文件,循环就打破了
在Python中,循环导入(Circular Import)通常发生在两个或多个模块相互导入对方时,导致依赖关系无法解析。这是一个常见的设计问题,可以通过以下几种核心方法解决:
1. 重构代码结构(最推荐)
将相互依赖的部分提取到第三个独立模块中,打破循环链。
# 重构前:a.py 和 b.py 相互导入
# a.py
import b
def func_a():
b.func_b()
# b.py
import a
def func_b():
a.func_a()
# 重构后:创建 c.py 存放公共依赖
# c.py
def common_func():
pass
# a.py
import c
def func_a():
c.common_func()
# b.py
import c
def func_b():
c.common_func()
2. 局部导入(Lazy Import)
在函数或方法内部进行导入,延迟导入时机。
# 修改前:模块级导入
# module_a.py
from module_b import ClassB
class ClassA:
def method(self):
return ClassB()
# 修改后:局部导入
# module_a.py
class ClassA:
def method(self):
from module_b import ClassB # 在需要时导入
return ClassB()
3. 使用类型注解与 from __future__ import annotations
Python 3.7+ 支持延迟评估注解,避免运行时导入依赖。
# __future__ 注解配合类型提示
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from module_b import ClassB # 仅用于类型检查,不实际导入
class ClassA:
def create_b(self) -> ClassB: # 类型提示正常使用
from module_b import ClassB # 实际导入放在函数内
return ClassB()
4. 合并模块
如果循环导入的模块紧密耦合,考虑合并为一个逻辑模块。
# 合并前:user.py 和 profile.py 相互依赖
# 合并后:account.py 包含所有相关类
class User:
def get_profile(self):
return Profile()
class Profile:
def get_user(self):
return User()
5. 依赖注入
通过参数传递依赖对象,而非直接导入模块。
# service_a.py
class ServiceA:
def process(self, service_b): # 接收ServiceB实例作为参数
return service_b.data
# service_b.py
class ServiceB:
def __init__(self):
self.data = "processed"
关键建议
- 优先采用重构和代码结构优化,这是最根本的解决方案。
- 使用
mypy或pylint等工具检测循环依赖。 - 遵循“依赖倒置原则”:高层模块不应依赖低层模块,二者都应依赖抽象。
总结:通过重构代码结构、使用局部导入或类型注解可有效解决循环导入。

