Pydantic模型导出:如何跳过默认值但保留指定字段?
Pydantic:强制保留特定默认字段在
skip_defaults=True的输出中 问题场景
假设我们有如下Pydantic模型:
class A(BaseModel): a : int = 1 b : int = 2 # 无论是否传入,始终返回`b` c : int = 3
需要调用inst.dict(skip_defaults=True)时,强制返回字段b(即使它是默认值),同时其他默认字段(如a、c)可以正常跳过。
用户尝试过以下无效方式:
dict(skip_defaults=True):正常跳过b,不符合需求dict(skip_defaults=True, include={'b'}):include仅保留非默认值的字段,因此b仍被跳过- 在模型Config中设置
include={"b"}:同样受skip_defaults规则限制,b作为默认值会被过滤
用户自行实现了重写dict方法的方案,但希望了解是否有更原生的Pydantic API实现方式:
class A(BaseModel): a : int = 1 b : int = 2 c : int = 3 def dict(self, *args, **kwargs): res = super(A,self).dict(*args, **kwargs) res.update(b=self.b) return res
实际业务场景
用Pydantic生成Tabulator.js的API参数:大部分字段匹配Tabulator的默认值,因此用skip_defaults避免重复默认值;但部分自定义默认值(如layout: "fitColumns")与Tabulator的默认值不同,必须强制返回该字段:
class TabulatorColumnOptions(BaseModel): title : str formatter : str = "string" headerFilter : str = "" sorter : str = "" class TabulatorTableOptions(BaseModel): # 始终通过`dict()`返回 layout : str = "fitColumns" columns : list[TabulatorColumnOptions]
解决方案
1. Pydantic v1:使用Field(always=True)
这是最简洁的原生方案,给目标字段添加always=True参数,该字段会始终被包含在输出中,不受skip_defaults规则影响:
from pydantic import BaseModel, Field class A(BaseModel): a: int = 1 b: int = Field(2, always=True) # 强制保留该字段 c: int = 3 # 测试 inst = A() print(inst.dict(skip_defaults=True)) # 输出: {'b': 2} inst2 = A(b=5) print(inst2.dict(skip_defaults=True)) # 输出: {'b': 5}
对应业务场景的模型修改:
class TabulatorTableOptions(BaseModel): layout: str = Field("fitColumns", always=True) # 强制返回该字段 columns: list[TabulatorColumnOptions]
2. Pydantic v2:自定义Dump方法
Pydantic v2移除了always=True参数,可通过自定义model_dump方法实现需求,同时保留灵活性:
from pydantic import BaseModel class BaseWithForcedFields(BaseModel): # 子类需定义该集合,指定强制保留的字段 _forced_fields = set() def model_dump(self, *args, exclude_unset=False, **kwargs): res = super().model_dump(*args, exclude_unset=exclude_unset, **kwargs) if exclude_unset: # 添加所有强制保留的字段 for field in self._forced_fields: res[field] = getattr(self, field) return res # 业务模型继承该基类 class TabulatorTableOptions(BaseWithForcedFields): _forced_fields = {'layout'} layout: str = "fitColumns" columns: list[TabulatorColumnOptions] # 测试 table_opts = TabulatorTableOptions(columns=[TabulatorColumnOptions(title="Name")]) print(table_opts.model_dump(exclude_unset=True)) # 输出: {'layout': 'fitColumns', 'columns': [{'title': 'Name'}]}
3. 优化版重写方法(通用兼容)
如果需要兼容不同版本的Pydantic,可以沿用用户的思路,但优化为通用基类,避免重复代码:
from pydantic import BaseModel class BaseForcedFieldsModel(BaseModel): _forced_fields = set() def dict(self, *args, skip_defaults=False, **kwargs): res = super().dict(*args, skip_defaults=skip_defaults, **kwargs) if skip_defaults: for field in self._forced_fields: res[field] = getattr(self, field) return res # 兼容Pydantic v2的model_dump def model_dump(self, *args, exclude_unset=False, **kwargs): res = super().model_dump(*args, exclude_unset=exclude_unset, **kwargs) if exclude_unset: for field in self._forced_fields: res[field] = getattr(self, field) return res # 使用示例 class A(BaseForcedFieldsModel): _forced_fields = {'b'} a: int = 1 b: int = 2 c: int = 3
总结
- Pydantic v1优先使用
Field(always=True),最符合原生API设计 - Pydantic v2推荐通过自定义基类的方式实现,灵活且可复用
- 用户自行重写
dict/model_dump的方案完全可行,优化为通用基类后更适合多模型场景
内容的提问来源于stack exchange,提问作者JL Peyret
相关产品推荐
相关产品推荐

