SQLModel与Pydantic BaseModel的exclude_unset行为差异及解决方法
差异根本原因
SQLModel 作为衔接 Pydantic 和 SQLAlchemy 的适配层,重写了原生 Pydantic 的模型初始化逻辑:
- 原生 Pydantic 只有字段被显式传参、或者在代码中手动赋值时,才会把字段加入
__fields_set__集合,exclude_unset参数就是基于这个集合做过滤,因此能准确筛掉未传参的默认字段。 - SQLModel 为了兼容 SQLAlchemy 的 ORM 行为,会在模型实例化时,自动把所有声明了默认值的字段都加入
__fields_set__集合——这个设计是为了避免 ORM 生成 SQL 时漏传字段,导致本该使用模型侧默认值的字段错误触发数据库侧默认值,但也直接导致依赖__fields_set__的exclude_unset过滤逻辑失效,哪怕字段没被显式传入、值为默认的None,也会被判定为「已设置」而保留在导出结果里。
正确导出方案
根据业务场景是否需要区分「显式传入None」和「未传参默认值为None」,可以选两种实现方式:
- 不需要区分两种场景时,直接组合参数过滤即可,这是最简单的方案:
# 同时开启exclude_unset和exclude_none,自动过滤所有值为None的字段 print(k.dict(exclude_unset=True, exclude_none=True)) # 输出结果:{'is_required': 0.1, 'a_float': 1.2}
- 需要区分两种场景(即显式传入的None需要保留,仅过滤未传参的默认None字段)时,可以自定义基类修正
__fields_set__的取值逻辑,和原生Pydantic行为对齐:
from typing import Any, Optional from sqlmodel import SQLModel class CustomBaseModel(SQLModel): def __init__(self, **data: Any): super().__init__(**data) # 仅将实际传入的字段标记为已设置 self.__fields_set__ = set(data.keys()) # 业务模型继承自定义基类即可 class Model(CustomBaseModel): is_required: float a_float: Optional[float] = None k: Optional[int] = None k = Model(**{"is_required": 0.1, "a_float": 1.2}) print(k.dict(exclude_unset=True)) # 输出结果:{'is_required': 0.1, 'a_float': 1.2} # 验证显式传None的场景 k2 = Model(**{"is_required": 0.1, "a_float": 1.2, "k": None}) print(k2.dict(exclude_unset=True)) # 输出结果:{'is_required': 0.1, 'a_float': 1.2, 'k': None}
注意:如果你的模型是绑定数据库表的表模型(设置了
table=True),使用上述自定义基类后,未传参的字段不会被模型侧赋值,插入数据时会触发数据库配置的字段默认值,使用前需要确认符合业务预期。
内容的提问来源于stack exchange,提问作者nuitnuit
相关产品推荐
相关产品推荐

