Python Pydantic 簡介:和 Dataclass 的區別,Alias 的簡易用法

當寫慣了 C++、Java、TypeScript 這類 Strong Typing 的語言時,想要在 Python 處理型別總會在剛開始不太習慣,因為 Python 雖然提供了 Typing,但是僅能「提示」,而不會真的拋出錯誤。

此時可以嘗試看看 Pydantic 這樣的 Library,在定義資料的時候能較為強制限制資料的格式。此篇簡單記錄 Pydantic 的基本用法,為何要用,又和原生的 Dataclass 有什麼區別。

Pydantic 的基礎功能:

  1. 檢查型別
  2. 自訂驗證
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pydantic import BaseModel

# Example 1
class User(BaseModel):
name: str
age: int

user = User(name="John", age="text") # Throw ValidationError as age is not int

# Example 2
class Product(BaseModel):
name: str
price: float = Field(gt=0)

product = Product(name="Apple", price=-10) # Throw ValidationError as price < 0

上述程式碼的兩個例子,分別建立了 User 和 Product 兩個類別。User 中定義 age 為 int 的型別,因此當我們 New Object 時,輸入 age="text" 時,Pydantic 能幫我們抓出型別上的錯誤。

再來則是第二個例子的 Field(gt=0) 則讓我們能透過 Pydnatic 自訂驗證,從而限制了 price 的數值要大於 0。

為何要用 Pydantic,和 Python 原生 Dataclass 有何差別?

前面提到了 Pydantic 能做到檢查型別、自訂驗證,而 Dataclass 則無法,我們就型別的驗證上來比較看看:

  1. 自動型別轉換

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    from pydantic import BaseModel, Field
    from dataclasses import dataclass

    # Pydantic
    class User1(BaseModel):
    name: str
    age: int

    user1 = User1(name="John", age="25")
    print(user1)

    # Dataclass
    @dataclass
    class User2:
    name: str
    age: int

    user2 = User2(name="John", age="25")
    print(user2)

    輸出結果為

    1
    2
    name='John' age=25
    User2(name='John', age='25')

    當我們都將 age 定義成 int,Pydantic 會自動將我們輸入錯的 "25" 這個字串轉換成整數,但是 Dataclass 並不會。

  2. 拋出錯誤

    1
    2
    user1 = User1(name="John", age="Doe") # Throw Error - age, Input should be a valid integer
    user2 = User2(name="John", age="Doe") # Doesn't throw Error

    用 Pydantic 的 User1 會在無法轉換型別時拋出錯誤,但是 Dataclass 並不會

使用 Pydantic 定義資料 Class 時,能自動做型別轉換、驗證,以及更複雜的邏輯檢查。當出現驗證失敗後,會直接拋出錯誤。

相較之下,原生的 Dataclass 僅僅是簡單的資料容器而已,如果真的需要驗證資料型態或是邏輯檢查,還需要手動處理、拋出錯誤。

使用 Pydantic Alias 改變 Field 名稱

我在使用 Python 撰寫資料轉換的程式碼時,對於原始資料和我們定義的資料,如果有 Key 不對齊的情況,則可以使用 Pydantic 好用的功能:Alias。

例如原始資料的某個 Key 為 tw, ny 等,但是我們想將自己定義的 Model key 寫清楚點,定義成 taiwan, new-york,則可以透過 Alias 達成。

首先先看一下,在 Python 中有一種符號為 ** 的 Unpacking Operators(開箱運算子),後續使用 Alias 的過程中會用到。** 可以使函式收 0 到多個 Key-Value Pairs,存入 Dict 中,如 def add_animals(**kwargs) 等等。

有了 Unpacking Operators,我們便能搭配 Pydantic 的 Alias 來處理外部資料,讓一些不太好讀的欄位和符合我們制定的 Pydantic Model。

舉個例子來說,我們想讀一份儲存 User 的 JSON 文件,裡面的格式如下:

1
2
3
4
[
{"1", 1, "2": "John"},
{"1", 2, "2": "Jane"}
]

其中的 Key 是不好讀的 12,然而代表的意思卻是 idname。此時我便能透過 Pydantic 的 Alias 做處理,首先定義這些 Fields 的 Alias 為 12 來符合資料來源的 Field Keys

1
2
3
4
5
from pydantic import BaseModel, Field

class User(BaseModel):
id: int = Field(alias="1")
name: str = Field(alias="2")

然後讀取 JSON 檔案中的資料,透過 ** 來 Unpack 這個 Dictionary 的資訊,然後丟入我們定義的 Pydantic User Model 中

1
2
3
4
data = {"1": 1, "2": "John"} # One element from the JSON file

user = User(**data)
print(user)

最後得到 id=1 name='John',就是將 Alias 轉換成我們 Model 中 Field Name 的結果。

參考資料

  1. Pydantic