pydantic 对象的陷阱

Table of Contents

陷阱

在 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 列表。

Comments |0|

Legend *) Required fields are marked
**) You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Category: 似水流年