在使用 Pydantic 构建数据模型时,我们经常需要在不创建实例的情况下,知道哪些字段是调用方必须传入的。Pydantic V2 提供了完善的元数据接口,让这件事变得非常简单且可靠。

为什么不能直接看实例属性
很多初学者会尝试先创建一个对象,再去看哪些属性没有默认值。这种做法有两个明显问题:第一,如果模型里有一些带复杂校验的必填字段,为了“探测”而实例化可能会触发校验错误;第二,实例化本身就需要提供必填项,这就变成了先有鸡还是先有蛋的问题。
Pydantic 的设计思路是把字段的定义信息和运行时的实例数据分开。模型类上的 model_fields 是一个字典,键是字段名,值是一个 FieldInfo 对象,里面记录了类型、默认值、是否必填等元信息。我们完全可以在类级别完成必需属性的提取。
使用 model_fields 获取必需属性
在 Pydantic V2 中,每个字段对应的 FieldInfo 都有一个 is_required() 方法,也可以直接访问其 required 属性。最直观的方式是遍历 model_fields,把 required 为 True 的字段名收集起来。
下面是一段可复用的工具函数示例,它接收一个 Pydantic 模型类,返回必需字段的列表:
from pydantic import BaseModel, Field
class UserProfile(BaseModel):
user_id: int
name: str = Field(default='匿名用户')
email: str
age: int | None = None
def get_required_fields(model_cls):
required = []
for field_name, field_info in model_cls.model_fields.items():
# required 为 True 表示调用方必须提供该字段
if field_info.required:
required.append(field_name)
return required
if __name__ == '__main__':
print(get_required_fields(UserProfile))
# 输出: ['user_id', 'email']
在上面的代码中,user_id 和 email 没有默认值,因此被标记为必需;而 name 和 age 由于设置了默认值或可选类型,不在结果中。这种方式不依赖任何实例化操作,执行效率很高。
处理 Optional 与带默认值的字段
有一个容易混淆的点:像 age: int | None = None 这样的字段,虽然类型是可选的,但因为给了默认值 None,所以并不是必需字段。反过来,如果写成 age: int | None 且没有默认值,那它依然是必需的,因为调用方必须显式传一个值(可以是 None)。
我们可以通过对比来验证这一行为:
from pydantic import BaseModel
class DemoModel(BaseModel):
a: int
b: int | None
c: int | None = None
for name, info in DemoModel.model_fields.items():
print(name, 'required =', info.required)
# 输出:
# a required = True
# b required = True
# c required = False
从输出可以看出,b 虽然是可选类型,但因为没有默认值,Pydantic 仍将其视为必需属性。这说明“必需”的判断标准是“是否必须传参”,而不是“类型是否可为空”。
在动态场景中的实际应用
获取必需属性在不少工程场景中都很有用。例如,在写通用的 API 参数解析层时,可以根据必需字段自动生成错误提示;或者在做配置加载器时,对缺失的必需配置提前报错,而不是等用到时才发现。
我们也可以把提取逻辑封装成类装饰器或 mixin,让所有模型自带 required_fields() 方法:
from pydantic import BaseModel
class WithRequired(BaseModel):
@classmethod
def required_fields(cls):
return [k for k, v in cls.model_fields.items() if v.required]
class Product(WithRequired):
sku: str
price: float
discount: float = 0.0
print(Product.required_fields())
# 输出: ['sku', 'price']
这种写法让模型自身具备 introspection 能力,调用方不需要知道 Pydantic 内部细节,直接通过语义化方法拿到必需字段。对于中大型项目,能明显减少重复代码。
与 Pydantic V1 的差异说明
如果你还在使用 Pydantic V1,获取必需属性的方式略有不同。V1 中通常使用 __fields__ 以及 field.required 属性,且对 Optional 的处理依赖 allow_none 标志。升级到 V2 后,统一使用 model_fields 和 FieldInfo.required,接口更加清晰。
如果需要在兼容层写代码,可以先判断是否存在 model_fields 属性:
def get_required(model_cls):
if hasattr(model_cls, 'model_fields'):
fields = model_cls.model_fields
else:
fields = model_cls.__fields__
return [k for k, v in fields.items() if getattr(v, 'required', False)]
# 在 V2 模型上调用
from pydantic import BaseModel
class Item(BaseModel):
id: int
tag: str = 'default'
print(get_required(Item))
# 输出: ['id']
通过这种兼容写法,老项目迁移时不必一次性改完所有调用点,可以平滑过渡到新版本。
小结
获取 Pydantic 对象中的必需属性,核心就是读取模型类的字段元数据,而非依赖实例。利用 model_fields 配合 required 标志,可以写出零成本、可读性高的工具函数。理解必需与可选的真正判定逻辑,能帮我们避开类型注解带来的直觉误区,也让动态校验和配置管理更加稳健。
Pydanticmodel_fieldsrequired_fields修改时间:2026-08-09 03:03:12