Pydantic开启validate_assignment时如何实现引用赋值而非副本?
Pydantic 2.0 赋值时保留引用的实现方案
问题场景与示例代码
# 可行但无赋值验证: from pydantic import BaseModel class One(BaseModel): field1: list class Two(One): field2: list def __init__(self, **kwargs): super().__init__(**kwargs) self.field1 = self.field2 # 能保留引用,但不会触发赋值验证 :( # 带赋值验证,但存在引用/副本问题 class OneV2(BaseModel): field1: list class Config: validate_assignment = True class TwoV2(OneV2): field2: list class Config: validate_assignment = True def __init__(self, **kwargs): super().__init__(**kwargs) self.field1 = self.field2 # 结果是深拷贝,而非引用赋值 # 测试 _2 = Two(field1=[1], field2=[1, 2, 3]) print(_2.field1 is _2.field2) # True,符合预期 _2_v2 = TwoV2(field1=[1, 2, 3], field2=[1, 2, 3, 4]) print(_2_v2.field1 is _2_v2.field2) # False,不符合预期
需求明确:在开启validate_assignment的TwoV2类中,将field2的引用直接赋值给field1,同时保留赋值验证功能。
解决方案
方法1:直接操作实例的__dict__
绕过Pydantic的赋值拦截逻辑,直接修改实例内部的属性存储:
from pydantic import BaseModel class OneV2(BaseModel): field1: list class Config: validate_assignment = True class TwoV2(OneV2): field2: list class Config: validate_assignment = True def __init__(self, **kwargs): super().__init__(**kwargs) # 直接修改__dict__,保留引用且不影响其他属性的验证 self.__dict__['field1'] = self.field2 # 测试验证 _2_v2 = TwoV2(field1=[1,2,3], field2=[4,5,6]) print(_2_v2.field1 is _2_v2.field2) # True,引用保留 # 验证仍生效:非法赋值会触发报错 try: _2_v2.field1 = "invalid type" except ValueError as e: print(e) # 输出验证错误信息
方法2:使用model_setattr并关闭单次验证
使用Pydantic官方提供的model_setattr方法,通过validate=False参数跳过当前赋值的验证,从而保留引用:
from pydantic import BaseModel class OneV2(BaseModel): field1: list class Config: validate_assignment = True class TwoV2(OneV2): field2: list class Config: validate_assignment = True def __init__(self, **kwargs): super().__init__(**kwargs) # 调用model_setattr,关闭本次赋值的验证 self.model_setattr('field1', self.field2, validate=False) # 测试验证 _2_v2 = TwoV2(field1=[1,2,3], field2=[4,5,6]) print(_2_v2.field1 is _2_v2.field2) # True,引用保留 # 其他赋值仍会触发验证 try: _2_v2.field2 = 123 except ValueError as e: print(e) # 输出验证错误信息
原理说明
当validate_assignment=True时,Pydantic会拦截所有属性赋值操作,对输入值进行验证、解析和深拷贝(针对列表、字典这类可变类型),目的是保证实例数据的一致性和不可变性。如果需要保留引用,必须绕过这个拦截逻辑:
- 直接操作
__dict__是最直接的方式,因为Pydantic实例的属性实际存储在__dict__中,跳过了验证拦截 model_setattr是官方推荐的属性设置方法,通过validate=False可以灵活跳过单次验证,比直接操作__dict__更符合Pydantic的设计规范
内容的提问来源于stack exchange,提问作者deponovo
相关产品推荐
相关产品推荐

