Pydantic v2动态键建模及属性访问方式优化求助
解决方案
核心思路
- 弃用
RootModel,改用直接继承BaseModel的方式,天然支持属性式(.)访问 - 通过Pydantic的模型配置和验证器,强制校验人员名称必须在指定列表内
- 利用
model_config = {'extra': 'forbid'}拦截未被允许的名称
静态字段实现(推荐,类型提示友好)
这种方式把所有可接受的名称声明为模型的可选字段,既能享受Pydantic的自动校验,又能直接用点访问属性:
import json from pydantic import BaseModel, conint, model_validator from typing import Optional class PersonModel(BaseModel): age: conint(ge=0) postcode: conint(ge=1000, le=9999) class PeopleModel(BaseModel): # 声明所有可接受的名称为可选PersonModel字段 Jack: Optional[PersonModel] = None Robert: Optional[PersonModel] = None Sarah: Optional[PersonModel] = None Alex: Optional[PersonModel] = None @model_validator(mode='after') def ensure_at_least_one_entry(cls, values): # 可选校验:确保至少有一个有效的人员数据 if not any(v is not None for v in values.__dict__.values()): raise ValueError("必须至少包含一个人员数据") return values class ParentModel(BaseModel): people: PeopleModel # 加载并验证JSON文件 file_path = "scratch/test.json" with open(file_path, "r") as f: raw_data = json.load(f) # 解析数据 validated_data = ParentModel(**raw_data) # 直接通过点访问属性 print(validated_data.people.Jack.age) # 输出: 32 print(validated_data.people.Robert.postcode) # 输出: 2354
动态键校验实现(适合名称列表需动态调整的场景)
如果可接受的名称列表需要频繁修改,不需要逐个声明字段,可通过自定义验证器和__getattr__实现:
import json from pydantic import BaseModel, conint, model_validator from typing import Dict # 可接受的名称列表 ACCEPTED_NAMES = ["Jack", "Robert", "Sarah", "Alex"] class PersonModel(BaseModel): age: conint(ge=0) postcode: conint(ge=1000, le=9999) class PeopleModel(BaseModel): model_config = {'extra': 'forbid'} # 禁止未被允许的键 @model_validator(mode='before') def validate_person_names(cls, values): if not isinstance(values, dict): raise ValueError("people字段必须是字典格式") # 检查所有键是否在允许列表内 invalid_names = [name for name in values if name not in ACCEPTED_NAMES] if invalid_names: raise ValueError(f"无效的人员名称: {', '.join(invalid_names)},允许的名称为: {', '.join(ACCEPTED_NAMES)}") return values # 实现__getattr__以支持点访问 def __getattr__(self, name): if name in self.__dict__: return self.__dict__[name] # 从模型的extra字段中获取已校验的人员数据 if name in self.model_extra: return PersonModel(**self.model_extra[name]) raise AttributeError(f"'PeopleModel' 对象没有属性 '{name}'") class ParentModel(BaseModel): people: Dict[str, PersonModel] # 加载数据 file_path = "scratch/test.json" with open(file_path, "r") as f: raw_data = json.load(f) # 先通过ParentModel校验结构,再转换为PeopleModel支持点访问 parent_model = ParentModel(**raw_data) people_model = PeopleModel(**parent_model.people) # 直接点访问 print(people_model.Sarah.age) # 输出: 55
两种方式对比
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 静态字段实现 | 类型提示完善,自动校验 | 名称列表修改需同步改模型 |
| 动态键校验实现 | 名称列表可动态调整 | 类型提示不够友好 |
内容的提问来源于stack exchange,提问作者Rash
相关产品推荐
相关产品推荐

