[note] Python Dataclasses
@dataclass()
@dataclass(frozen=True) # 讓 instance variable 變成 immutable
Basic
from dataclasses import dataclass
@dataclass
class Circle:
x: int = 0
y: int = 0
radius: int = 1
c = Circle() # Circle(x=0, y=0, radius=1)
# 自動建立 __repr__
# 沒用 @dataclass 的話會得到 <__main__.Circle object at 0x1091b8e30>
c # Circle(x=0, y=0, radius=1)
# 把 class attributes 自動變成 instance variables
# 沒用 @dataclass 會得到 {}
c.__dict__ # {'x': 0, 'y': 0, 'radius': 1}
# 不用另外定義 `__init__` 就可以帶入參數
c1 = Circle(1, 1, 3)
c2 = Circle(1, 1, 3)
# 兩個 instance 如果使用 `==` 比較時,會比較裡面的 value 是不是都一樣
c1 == c2 # True
Dataclass 會:
- 自動建立
__repr__
- 把 class attributes 自動變成 instance variables
- 承上,所以不用另外定義
__init__
就可以帶入參數 - 兩個 instance 如果使用
==
比較時,會比較裡面的 value 是不是都一樣
如果沒有使用 dataclass 需要自己實作:
class Circle:
# 要在 __init__ 中定義 instance variable
def __init__(self, x: int = 0, y: int = 0, radius: int = 1):
self.x = x
self.y = y
self.radius = radius
# 要自己寫 __repr__
def __repr__(self):
return f"{self.__class__.__qualname__}(x={self.x}, y={self.y}, radius={self.radius})"
# 需要加上這個才能判斷兩個物件中的 value 是否一樣
def __eq__(self, other):
if self.__class__ == other.__class__:
return (self.x, self.y, self.radius) == (other.x, other.y, other.radius)
return NotImplemented
asdict、astuple
透過 @dataclass
建立的物件,可以使用 dataclasses
提供的 asdict
和 astuple
方法來將物件轉成 dict 或 tuple:
from dataclasses import dataclass, asdict, astuple
@dataclass()
class CircleD:
x: int = 0
y: int = 0
radius: int = 1
c1 = CircleD(1, 2, 2)
c2 = CircleD(1, 2, 3)
asdict(c1) # {'x': 1, 'y': 2, 'radius': 2}
astuple(c2) # (1, 2, 3)
fields
透過 @dataclass
建立的物件,可以使用 dataclasses
提供的 fields
來取得各欄位的資料:
from dataclasses import dataclass, fields
@dataclass()
class CircleD:
x: int = 0
y: int = 0
radius: int = 1
c1 = CircleD(1, 2, 2)
for field in fields(c1):
print(field, end="\n----\n")
"""
Field(name='x',type=<class 'int'>,default=0,default_factory=<dataclasses._MISSING_TYPE object at 0x103e2f500>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)
----
Field(name='y',type=<class 'int'>,default=0,default_factory=<dataclasses._MISSING_TYPE object at 0x103e2f500>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)
----
Field(name='radius',type=<class 'int'>,default=1,default_factory=<dataclasses._MISSING_TYPE object at 0x103e2f500>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)
----
"""
frozen
在 @dataclass
中可以帶入參數 frozen=True
,如此:
- 建立出來的 instance variable 將會是 immutable
- 承上,建立出來的 object 會是 hashable 的
@dataclass(frozen=True)
class CircleD:
x: int = 0
y: int = 0
radius: int = 1
c1 = CircleD(1, 2, 3)
# 建立出來的 object 會是 hashable 的
hash(c1) # 529344067295497451
c1.x = 3 # FrozenInstanceError: cannot assign to field 'x'
keyword only
如果希望使用者在 initialized instance 時,一定要用 keyword arguments 的寫法,可以使用 kw_only=True
:
@dataclass(kw_only=True)
class CircleD:
x: int = 0
y: int = 0
radius: int = 1
c1 = CircleD(x=1, y=2, radius=3)
order
在 @dataclass
中可以帶入參數 order=True
,如此將可以比較兩個 object 中資料的大小(以 tuple 的方式進行比較):
@dataclass(frozen=True,order=True)
class CircleD:
x: int = 0
y: int = 0
radius: int = 1
c1 = CircleD(1, 2, 2)
c2 = CircleD(1, 2, 3)
c1 > c2 # False,比較 (1, 2, 2) > (1, 2, 3)