目錄

  • 前言
  • 起源與初探
    • 傳統方法
    • 初探與基本
  • 進階用法
    • 預設值和型態
    • 修改 magic method
  • 結論

前言

物件導向是現代程式語言必備的要素,Python 也不例外。不過,早期的設計複雜,需要撰寫大量重複的 code,不但不符合 DRY(Don't Repeat Yourself)原則,還降低了可讀性。
Python 3.7 最重要的改動就是推出了 Data Classes,儘管在先前引發了一陣風波—有關於 Guido 為什麼不直接合併 attr.s 這個第三方套件,但 Data Classes 仍然是影響 Python 生態的重要改革。

起源與初探

傳統方法

在 Python 3.7 之前的版本,如果要在 Python 中撰寫資料的類別,大概會長這樣

class Node:
    def __init__(self, x, y, radius, name):
        self.x = x
        self.y = y
        self.radius = radius
        self.name = name

你會注意到這段 code 當中,每個屬性被重複提到了 3 次,如果有更多屬性要放在這個 class,可讀性和可維護性就會開始降低。

而且,如果想要利用 print() 來觀察這個 class,就需要再寫一個 __repr__

    def __repr__(self):
        return 'Node(x={}, y={}, radius={}, name={})'.format(
            self.x, self.y, self.radius, self.name
        )

除此之外,實務中的 __eq__ __add__ to_dict to_json 這些 function 常常用到,如果每個 class 都要寫一遍,對工程師來說是不小的工作量。
Data Classes 則能夠解決這些問題

初探與基本

Python 3.7 開始會內建 Data Classes,之後 Python 3.6 也能透過 pip 安裝。

Data Classes 中,上面的 Class 可以這樣改寫

from dataclasses import dataclass

@dataclass
class Node:
    x: int
    y: int
    radius: float
    name: str

使用 dataclass 裝飾器可以宣告一個特殊的 class,按照格式輸入屬性和限定的型態,你的 class 便會擁有基本的功能,包含 __repr__ __eq__ 幾個常用的 magic method

from dataclasses import asdict

node = Node(2, 4, 5.3, 'start')
print(node)
# Node(x=2, y=4, radius=5.3, name='start')
print(node.radius)
# 5.4
print(node == Node(7, 4, 2.2, 'start'))
# False
print(asdict(node))
# {'x': 2, 'y': 4, 'radius': 5.3, 'name': 'start'}

是不是相當方便呢

進階用法

預設值和型態

Data Class 支援預設值 和 更多樣化的型別定義,例如

from dataclasses import dataclass, field
from typing import Any, List

@dataclass
class Node:
    x: int
    y: int
    radius: float = 0.0
    name: str = 'none'
    child: Any = None
    test: List[int] = field(default_factory=list)

這段 code 應該很好理解,名稱後用 : 宣告型態,型態在 typing 套件中更多支援(例如這邊的 Any),在型態後面用 = 賦予預設值。

暫時忽略最後一行的那個 field,這段 code 可以像是正常使用 class 一樣,用順序或是 keywords 輸入數值

print(Node(2, 4))
# Node(x=2, y=4, radius=0.0, name='none', child=None, test=[])
print(Node(2, 4, name='end', radius=1.5))
# Node(x=2, y=4, radius=1.5, name='end', child=None, test=[])
print(Node(4, 6, 5.3, 'hi', test=[1, 2, 3]))
# Node(x=4, y=6, radius=5.3, name='hi', child=None, test=[1, 2, 3])

修改 magic method

前面有提到,Data Class 會自動加入 magic method,如果我們要對他們進行設定與微調,例如 __repr__ 不要顯示某個變數,__eq__ 不要比較某個變數,我們可以使用 field 來進行更多設定

from dataclasses import dataclass, field
from typing import Any

@dataclass
class Node:
    x1: int
    x2: int = field(compare=False)
    y1: str = field(default=33)
    y2: Any = field(default=None, repr=False)

如此一來 y2 不會顯示在 print(),__eq__ 不會比較 x2,y1 y2 有了預設值,實際執行如下

node = Node(2, 9)
print(node)
# Node(x1=2, x2=9, y1=33)
print(node == Node(2, 11))
# True 因為 x2 有 compare=False 不會被拿來比較
print(node == Node(2, 45, 88))
# False

結論

我沒有用過 attr,沒辦法比較,不過 Data Class 看起來還不錯,有解決了一些實務上的問題,減少 code 數量,而且也沒有令可讀性下降。如果能探究更多進階用法,應該能讓開發 Python 專案更加順利。

參考資料

系列介紹