为什么 pydantic 中的可变对象没有随着修改变化呢?
from datetime import datetimefrom 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 datetimefrom 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