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中如何解决循环导入的问题?小白求教

9 回复

.
├── 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"

关键建议

  • 优先采用重构和代码结构优化,这是最根本的解决方案。
  • 使用 mypypylint 等工具检测循环依赖。
  • 遵循“依赖倒置原则”:高层模块不应依赖低层模块,二者都应依赖抽象。

总结:通过重构代码结构、使用局部导入或类型注解可有效解决循环导入。

回到顶部