如何为Pydantic V2 BaseModel添加类级__getattr__行为?
解决方案:安全实现Pydantic V2类属性访问生成过滤对象
核心思路
避免直接覆盖元类的__getattr__与Pydantic内部逻辑冲突,而是在自定义元类中先委托Pydantic处理属性访问,仅当Pydantic无法找到属性时,再检查是否为模型字段并返回过滤对象,同时保证多级子类继承时逻辑正常生效。
具体实现步骤
1. 自定义元类继承ModelMetaclass
from pydantic._internal._model_construction import ModelMetaclass from pydantic import BaseModel class MetaGetFilterPydantic(ModelMetaclass): def __getattr__(cls, name): try: # 优先让Pydantic元类处理属性访问,保障内部逻辑正常 return super().__getattr__(name) except AttributeError: # 检查属性是否为当前模型的有效字段 if name in cls.model_fields: # 返回自定义过滤对象 return MyFilterObject(cls, name) # 非字段属性则抛出原错误 raise
2. 定义基础模型类
让你的ORM基础模型使用上述自定义元类:
class Model(BaseModel, metaclass=MetaGetFilterPydantic): class Config: # 根据业务需求配置Pydantic选项,比如冻结模型等 pass
3. 处理多级子类继承问题
若多级子类仍出现失效情况,可在元类的__new__方法中确保模型构建完成后字段信息同步:
class MetaGetFilterPydantic(ModelMetaclass): def __new__(mcs, name, bases, namespace, **kwargs): # 先让Pydantic完成模型类的初始化 cls = super().__new__(mcs, name, bases, namespace, **kwargs) # 可选:提前同步继承的字段信息,确保多级子类字段可被识别 return cls def __getattr__(cls, name): try: return super().__getattr__(name) except AttributeError: if name in cls.model_fields: return MyFilterObject(cls, name) raise
4. 实现过滤对象逻辑
MyFilterObject需接收模型类和字段名,用于后续生成查询条件:
class MyFilterObject: def __init__(self, model_cls, field_name): self.model_cls = model_cls self.field_name = field_name def __gt__(self, value): return {"$gt": {self.field_name: value}} def __eq__(self, value): return {"$eq": {self.field_name: value}} def __and__(self, other): return {"$and": [self, other]} # 根据业务需求重载其他操作符(如__lt__、__or__等)
关键注意事项
- 不可跳过Pydantic属性处理:必须优先调用
super().__getattr__,否则会破坏Pydantic内部的字段访问、模型配置等核心逻辑。 - 依赖model_fields而非__annotations__:Pydantic V2中
model_fields是处理后的完整字段集合,包含继承字段、默认值等信息,比直接使用__annotations__更可靠。 - 多级子类自动继承元类:由于基础
Model已指定自定义元类,所有继承自Model的子类都会自动使用该元类,无需额外配置。
内容的提问来源于stack exchange,提问作者Fredrik Nilsson
相关产品推荐
相关产品推荐

