pydantic 对象的陷阱

Posted on Mon 25 November 2024 in Journal

Abstract pydantic 对象的陷阱
Authors Walter Fan
 Category    learning note  
Status v1.0
Updated 2024-11-25
License CC-BY-NC-ND 4.0

陷阱

在 Python 中,类成员在所有实例之间是共享的,例如以下代码,friends 是一个类成员(而不是实例成员),所有 User 实例都会共享同一个 friends 列表。也就是说,对一个实例的 friends 列表进行修改,会影响到其他实例的 friends 列表。

这是因为 friends 是在类定义时定义的,是一个类级别的属性。

示例代码

from datetime import datetime
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str = "Walter Fan"
    signup_ts: datetime | None = None
    friends: list[int] = []

user1 = User(id=1)
user2 = User(id=2)

user1.friends.append(2)
print(user1.friends)  # [2]
print(user2.friends)  # [2] (意料之外的结果,因为两者共享了同一个 friends 列表)

解决方法

如果希望 friends 是每个实例独立的,应该将其设为实例属性,而不是类属性。你可以通过 Pydantic 的 Field 和默认工厂实现这一点:

修正代码

from datetime import datetime
from pydantic import BaseModel, Field

class User(BaseModel):
    id: int
    name: str = "Walter Fan"
    signup_ts: datetime | None = None
    friends: list[int] = Field(default_factory=list)  # 使用 default_factory 创建独立的列表

user1 = User(id=1)
user2 = User(id=2)

user1.friends.append(2)
print(user1.friends)  # [2]
print(user2.friends)  # [] (正确,两个实例有独立的 friends 列表)

原因

  • 使用 Field(default_factory=list) 时,list 是通过 default_factory 动态生成的,每次实例化时都会创建一个新的独立列表。
  • 如果直接用 friends: list[int] = [],[] 是在类加载时定义的共享对象。

总结

你的原始代码中,friends 列表会在实例之间共享,因此修改其中一个实例的 friends 会影响到所有实例。使用 Field(default_factory=list) 可以避免此问题,每个实例都有独立的 friends 列表。


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。