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 国际许可协议进行许可。