簡體   English   中英

為什么 pydantic 數據類將列表轉換為字典? 如何防止這種行為?

[英]Why does the pydantic dataclass cast a list to a dict? How to prevent this behavior?

我對 pydantic 數據類的行為有點困惑。 為什么dict類型接受dict list作為有效dict ,為什么將其轉換為鍵dict 難道我做錯了什么? 這是某種有意為之的行為嗎?如果是,是否有辦法阻止這種行為?

代碼示例:

from pydantic.dataclasses import dataclass

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

Output:

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

哈,這其實有點有趣。 忍受我...


為什么dict類型接受dict list作為有效dict ,為什么將其轉換為鍵dict

當我們仔細查看 Pydantic 模型使用的默認dict_validator時,就可以解釋這一點。 它所做的第一件事(對任何非dict )是試圖將值強制轉換為dict

用你的具體例子試試這個:

y = [{'a': 'b', 'c': 'd'}]
assert dict(y) == {'a': 'c'}

這是為什么?

好吧,要初始化dict ,您可以傳遞不同種類的 arguments。一種選擇是傳遞一些Iterable

iterable 中的每個項目本身必須是一個恰好有兩個對象的 iterable。 每個項目的第一個 object 成為新字典中的鍵,第二個 object 成為對應的值。

在您的示例中,您恰好有一個可迭代對象(特別是list ),並且該可迭代對象中唯一的項目本身就是一個可迭代對象(特別是dict )。 字典是如何默認迭代的? 通過他們的鑰匙! 由於該字典{'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}

您的情況下,這會導致您顯示的結果,乍一看似乎很奇怪且出乎意料,但當您考慮字典初始化的邏輯時,實際上是有道理的。

有趣的是,這list中的dict恰好有兩個鍵值對時才有效。 多一點或少一點都會導致錯誤。 (自己試試吧。)

簡而言之:這種行為既不是 Pydantic 也不是數據類的特殊行為,而是常規dict初始化的結果。


難道我做錯了什么?

我會說,是的。 您嘗試分配給Xy的值是一個list ,但您聲明它是一個dict 所以這顯然是錯誤的。 我知道有時數據來自外部來源,所以這可能不取決於你。


這是某種有意為之的行為嗎[...]?

這是一個很好的問題,因為我很想知道 Pydantic 團隊是否知道這種邊緣情況及其導致的奇怪結果。 我會說,字典驗證器的實現方式至少是可以理解的。


有沒有辦法防止這種行為?

是的。 除了不在那里傳遞列表的明顯解決方案之外。

您可以添加自己的自定義驗證器,將其配置為pre=True並讓它例如允許實際直接的dict實例繼續進行進一步驗證。 然后你可以立即捕獲這個錯誤。


希望這可以幫助。

感謝您對此有所啟發,因為一開始這也會讓我失望。 我想我會開始深入研究 Pydantic 問題跟蹤器和 PR,看看是否可以/應該/將以某種方式解決這個問題。

聚苯乙烯

這是前面提到的“嚴格”驗證器的非常簡單的實現,它可以防止dict -coercion 並立即為非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'}]))

Output:

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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM