目錄
- 前言
- 起源與初探
- 傳統方法
- 初探與基本
- 進階用法
- 預設值和型態
- 修改 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 專案更加順利。
參考資料
- Python之美 - attrs 和 Python3.7 的 dataclasses
https://zhuanlan.zhihu.com/p/34963159 - Ultimate Guide to Data Classes in Python 3.7
https://realpython.com/python-data-classes/
系列介紹
- Python 3.7特色 (0)-版本介紹
- Python 3.7特色 (0.5)-定位與特色
- Python 3.7特色 (1)-dataclass
- Python 3.7特色 (2)-breakpoint()
- Python 3.7特色 (3)-優化(time.ns、force UTF-8)
- Python 3.7特色 (4)-Cython(Thread local、pyc)(暫定)