哈哈,这其实挺好笑的.请容忍我...
为什么dict
类型接受dict
的list
为有效的dict
,为什么它被转换为dict
的密钥?
当我们更仔细地看一下Pydtic模型使用的默认dict_validator
时,就可以解释这一点.它做的第一件事(对于任何非dict
)是试图将值强制为dict
.
用你的具体例子来试一试:
y = [{'a': 'b', 'c': 'd'}]
assert dict(y) == {'a': 'c'}
为什么会这样呢?
要初始化dict
,可以传递不同类型的参数.一种 Select 是通过一些Iterable
和
Iterable中的每一项本身都必须是具有两个对象的Iterable.每个条目的第一个对象成为新词典中的关键字,第二个对象成为相应的值.
在您的示例中,恰好有一个Iterable(特别是list
),而该Iterable中唯一的项本身就是一个Iterable(特别是dict
).默认情况下,词典是如何迭代的?通过他们的 keys !由于词典{'a': 'b', 'c': 'd'}
正好具有两个键-值对,这意味着当在其上迭代时产生这两个键,即"a"
和"c"
:
d = {'a': 'b', 'c': 'd'}
assert tuple(iter(d)) == ('a', 'c')
正是这种机制允许例如从如下所示的2元组列表中构造dict
:
data = [('a', 1), ('b', 2)]
assert dict(data) == {'a': 1, 'b': 2}
在your种情况下,这会导致您显示的结果,乍一看似乎很奇怪和意外,但当您考虑字典初始化的逻辑时,实际上是有意义的.
有趣的是,当list
中的dict
有exactly个两个键-值对时,only就可以工作了!任何多多少少都会导致错误.(你自己试试吧.)
因此,简而言之:这种行为既不是Pydtic的特殊行为,也不是数据类的特殊行为,而是常规dict
初始化的结果.
我是不是做错了什么?
我会说,是的.您try 分配给X.y
的值是list
,但您将其声明为dict
.因此,这显然是错误的.我知道,有时数据来自外部来源,所以可能不是你说了算.
这是某种故意的行为吗[...]?
这是一个很好的问题,因为我很想知道皮丹蒂克团队是否意识到了这种边缘情况及其导致的奇怪结果.我想说,词典验证器是这样实现的,这至少是可以理解的.
有没有办法防止这种行为?
是.除了一个显而易见的解决方案,就是不在那里传递列表.
您可以添加您自己的自定义验证器,将其配置为pre=True
,并让它例如only允许实际的dict
个实例继续进行进一步的验证.然后,您就可以立即捕捉到这个错误.
希望这能帮上忙.
谢谢你对这件事的关注,因为一开始我也会被吓跑的.我想我会开始深入研究PYDANIC问题跟踪器和公关,看看这个问题是否可以/应该/将以某种方式得到解决.
PS
下面是前面提到的"严格"验证器的非常简单的实现,它防止了dict
强制,而是立即引发了非dict
的错误:
from typing import Any
from pydantic.class_validators import validator
from pydantic.dataclasses import dataclass
from pydantic.fields import ModelField, SHAPE_DICT, SHAPE_SINGLETON
@dataclass
class X:
y: dict
@validator("*", pre=True)
def strict_dict(cls, v: Any, field: ModelField) -> Any:
declared_dict_type = (
field.type_ is dict and field.shape is SHAPE_SINGLETON
or field.shape is SHAPE_DICT
)
if declared_dict_type and not isinstance(v, dict):
raise TypeError(f"value must be a `dict`, got {type(v)}")
return v
if __name__ == '__main__':
print(X([{'a': 'b', 'c': 'd'}]))
输出:
Traceback (most recent call last):
File "....py", line 24, in <module>
print(X([{'a': 'b', 'c': 'd'}]))
File "pydantic/dataclasses.py", line 313, in pydantic.dataclasses._add_pydantic_validation_attributes.new_init
File "pydantic/dataclasses.py", line 416, in pydantic.dataclasses._dataclass_validate_values
# worries about external callers.
pydantic.error_wrappers.ValidationError: 1 validation error for X
y
value must be a `dict`, got <class 'list'> (type=type_error)