You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 16:45:16