我对这个简单的数据类的行为感到有点困惑. 为什么dict类型接受dictlist为有效的dict,为什么它被转换为dict的密钥? 我是不是做错了什么?这是某种故意的行为吗?如果是的话,有什么方法可以防止这种行为吗?

Code Example:

from pydantic.dataclasses import dataclass

@dataclass
class X:
    y: dict
print(X([{'a':'b', 'c':'d'}]))

Output:

X(y={'a': 'c'})

推荐答案

哈哈,这其实挺好笑的.请容忍我...


为什么dict类型接受dictlist为有效的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中的dictexactly个两个键-值对时,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)

Python相关问答推荐

试图找到Python方法来部分填充numpy数组

查找两极rame中组之间的所有差异

将图像拖到另一个图像

处理带有间隙(空)的duckDB上的重复副本并有效填充它们

基于索引值的Pandas DataFrame条件填充

从spaCy的句子中提取日期

Pandas—在数据透视表中占总数的百分比

如何在达到end_time时自动将状态字段从1更改为0

为什么'if x is None:pass'比'x is None'单独使用更快?

为什么调用函数的值和次数不同,递归在代码中是如何工作的?

Python—为什么我的代码返回一个TypeError

30个非DATETIME天内的累计金额

如何在一组行中找到循环?

PySpark:如何最有效地读取不同列位置的多个CSV文件

Pandas:计数器的滚动和,复位

用由数据帧的相应元素形成的列表的函数来替换列的行中的值

文本溢出了Kivy的视区

用LAKEF划分实木地板AWS Wrangler

Pandas 数据框自定义排序功能

401使用有效的OAuth令牌向Google Apps脚本Web App发出POST请求时出现未经授权的错误(";