如何避免构造Pydantic对象时复制实例属性?
问题描述
尝试构造一系列Pydantic对象,希望它们的某一属性都指向同一个已实例化的特定值,但Pydantic构造函数总会为每个对象分配该实例的副本。曾尝试在AttributeClass中添加Config类,测试copy_on_model_validation = False等选项,但均无效。
环境:Python 3.11、Pydantic 1.10(2.4版本也存在相同问题)
示例代码:
from abc import ABC from pydantic import BaseModel class Solver(BaseModel): pass class MyClass(Solver): chain: tuple | None = None class AttributeClass(Solver): myvar: float | None = None class System(ABC): solver = None one_var = 1 chain =[AttributeClass(myvar=one_var) for _ in range(1,5)] class MySystem(System): solver = MyClass(chain=chain) if __name__ == "__main__": my_instance = MySystem() print("original ID: ", id(one_var)) for i,s in enumerate(my_instance.solver.chain): print(f"ID n° {i+1}: {id(s.myvar)}")
问题根源
你的代码中存在类型不匹配:one_var是int类型,但AttributeClass的myvar声明为float | None。Pydantic在验证时会自动将int转换为float,生成新的float实例(比如1.0),导致每个myvar的ID与原one_var不同,看起来像是被复制了。
另外,copy_on_model_validation=False对不可变类型(如int、float)的效果有限,因为这类类型本身的赋值行为在Python中就是值传递,且小整数会被缓存,但类型转换会打破这个逻辑。
解决方案
1. 修正类型匹配
将AttributeClass的myvar类型改为包含int,让Pydantic无需进行类型转换:
class AttributeClass(Solver): myvar: int | float | None = None
此时one_var的int类型会直接被保留,由于Python对小整数的缓存机制,所有myvar都会指向同一个实例,ID与原one_var一致。
2. 使用自定义字段跳过类型转换(针对必须保持原类型引用的场景)
如果需要严格保持原实例的引用,即使类型不完全匹配,可以使用Pydantic的自定义验证逻辑跳过自动类型转换:
Pydantic 2.x版本:
from pydantic import BaseModel, Field, PlainSerializer class AttributeClass(Solver): myvar: float | None = Field( default=None, serializer=PlainSerializer(lambda x: x, return_type=float if isinstance(x, (int, float)) else None) )
Pydantic 1.x版本:
from pydantic import BaseModel, validator class AttributeClass(Solver): myvar: float | None = None @validator('myvar', pre=True) def keep_original_instance(cls, v): # 直接返回原实例,不进行类型转换 return v
3. 使用可变容器封装值
如果需要保持对同一个值的引用(比如后续可能修改值并同步到所有对象),可以将不可变值封装到可变容器(如自定义类)中:
class ValueContainer: def __init__(self, value): self.value = value one_var = ValueContainer(1) class AttributeClass(Solver): myvar: ValueContainer | None = None
此时所有AttributeClass实例的myvar都会指向同一个ValueContainer实例,修改one_var.value会同步到所有对象的myvar.value。
内容的提问来源于stack exchange,提问作者djo

