为什么 pydantic 中的可变对象没有随着修改变化呢?

为什么 pydantic 中的可变对象没有随着修改变化呢?

from datetime import datetime

from typing import List, Optional

from pydantic import BaseModel

class User(BaseModel):

friends: List[int] = []

user_1 = User()

user_1.friends.append(1)

print(user_1.friends)

user_2 = User()

print(user_2.friends)

上面的代码,运行后输出如下:

[1]

[]

我有一个疑问,就是 friends 的默认值是一个 [] 空列表,通过前后两次实例化,两个实例对象持有的 friends 为什么指向的不是同一个 list 呢?


如果去掉继承 BaseModel,输出的两个就都是 [1]

from datetime import datetime

from typing import List, Optional

from pydantic import BaseModel

class User():

friends: List[int] = []

user_1 = User()

user_1.friends.append(1)

print(user_1.friends)

user_2 = User()

print(user_2.friends)

输出

[1]

[1]

pydantic 的 BaseModel 施加了什么“魔法”?


回答:

主要是默认值带来的问题。

列表是可变对象,当没有指定默认值时,每个实例都会创建一个新的空列表对象。但是,在指定了默认值后,所有实例都会共享同一个默认值对象,因为默认值只会创建一次并在所有实例之间共享。因此,user_1和user_2的属性friends内存指向相同。

如果继承了BaseModel,每个实例都会创建一个新的默认值对象,因为Pydantic会在内部创建一个新的配置类,以确保每个实例都有自己的默认值。因此如果实例化多个User类,并访问它们的friends属性,每个实例的friends属性都应该具有不同的内存地址。

pydantic/main.py(早期的实现,为了更容易说明这个问题)中,可以看到这个过程:

class ModelMetaclass(type):

def __new__(mcs, name, bases, namespace):

cls = super().__new__(mcs, name, bases, namespace)

if not is_base(cls):

config = getattr(cls, 'Config', BaseConfig)

cls.__config__ = config

cls.__fields__ = {}

cls.__validators__ = {}

cls.__initialised__ = False

cls.__post_init_original__ = getattr(cls, '__post_init__', None)

cls.__pre_root_validators__ = getattr(cls, '__pre_root_validators__', [])

cls.__pre_validators__ = getattr(cls, '__pre_validators__', {})

cls.__root_validators__ = getattr(cls, '__root_validators__', [])

cls.__validators__ = getattr(cls, '__validators__', {})

cls.__custom_root_type__ = None

cls.__custom_root_type_params__ = {}

cls.__json_encoder__ = getattr(cls, '__json_encoder__', None)

cls.__fields_set__ = set()

cls.__hash__ = None

cls.__module__ = namespace.get('__module__')

cls.__name__ = namespace.get('__qualname__') or name

cls.__annotations__ = namespace.get('__annotations__', {})

# Set up fields and validators

for name, value in namespace.items():

if isinstance(value, Field):

value.name = name

cls.__fields__[name] = value

elif isinstance(value, Validator):

cls.__validators__.setdefault(name, []).append(value)

cls.__validators__.update(cls.__pre_validators__)

cls.__validators__['__root__'] = cls.__pre_root_validators__ + cls.__root_validators__

# Set up field defaults

for f in cls.__fields__.values():

if f.has_default():

setattr(cls, f.name, f.default)

cls.__initialised__ = True

return cls

在这个元类中,当创建一个新的类时,会检查这个类是否继承自BaseModel。如果是,则在内部创建一个新的配置类,并将该类的默认值、验证器等信息保存在类的属性中。这样,每个实例都会有自己的默认值对象,从而确保其属性内存指向不同。

如果是最新的版本,可能是出于性能以及声明式范式的考虑,对这个过程实现做了分离。


回答:

class User(BaseModel):

id: int

name = 'John Doe'

signup_ts: Optional[datetime] = None

friends: list[int] = []

external_data = {

'id': '123',

'signup_ts': '2019-06-01 12:22',

'friends': [1, 2, '3'],

}

print(repr(user.signup_ts))

#> datetime.datetime(2019, 6, 1, 12, 22)

这个是官方的示例,可以看到在定义了signup_ts为datetime对象之后,传入了'2019-06-01 12:22'这么一个字符串,最后会将这个字符串转为datetime对象

我虽然没有深入pydantic的源码,但大致猜测一下,内部有针对常用的对象做转换。
[]这个也就不奇怪了,可能内部在构造对象的时候有一个copy.deepcopy的逻辑

以上是 为什么 pydantic 中的可变对象没有随着修改变化呢? 的全部内容, 来源链接: utcz.com/p/938897.html

回到顶部