dataclasses는 heterogeneous(이질간의)
collection 변수를 나타낸다. 즉, 다른 타입들의 변수들을 하나의 변수로 묶어 놓은 것이다. composite type으로 이는 서로 다른 타입을 가진 변수들을 어떠한 그룹으로 묶어내거나 이들 간의 관계를 나타내기 위해 만든다.
dataclass
는 python3.7부터 도입되었으며 dataclasses
모듈에 있다. 사용방법은 @dataclass
annotation으로 class를 annotate하면 된다. 필요한 것은 class
의 변수들인데, 이 변수들은 dataclass
가 맴버 변수로 만든다.
from dataclasses import dataclass
@dataclass
class MyFraction:
numerator: int = 0
denominator: int = 1
오해하지말자. numerator
와 denominator
는 클래스 변수가 아니라 인스턴스의 맴버 변수가 되는 것이다. dataclass
는 자동으로 이 맴버변수들의 생성자를 만들어줌으로 다음과 같이 호출이 가능하다. 만약, 생성자에서 이 값들을 넣어주지 않으면 기본 값이 들어간다.
fraction1 = MyFraction()
fraction2 = MyFraction(1,5)
print(fraction1) # MyFraction(numerator=0, denominator=1)
print(fraction2) # MyFraction(numerator=1, denominator=5)
dataclass
를 사용하면 두 가지 special methods를 얻을 수 있는데, __str__
과 __repr__
이다. 위에서 print
로 출력했을 때 MyFraction(numerator=1, denominator=5)
이런 결과가 나온 것은 __str__
과 __repr__
magic method 덕분이다.
또한 동등성 검사(equality)도 할 수 있다. 물론 동등성 검사를 위해서는 @dataclass
의 eq
옵션을 True
로 설정해주어야 한다.
from dataclasses import dataclass
@dataclass(eq=True)
class MyFraction:
numerator: int = 0
denominator: int = 1
fraction1 = MyFraction(1,5)
fraction2 = MyFraction(1,5)
print(fraction1 == fraction2) # True
이는 magic method인 __eq__
함수를 dataclass
에서 설정해주었기 때문이다.
추가적으로 relational comparision도 가능하다. 즉, 비교가 가능하다는 것이다. 이를 위해서는 @dataclass
어노테이션에 order
를 True
로 설정해주어야 한다.
from dataclasses import dataclass
@dataclass(eq=True, order=True)
class MyFraction:
numerator: int = 0
denominator: int = 1
fraction1 = MyFraction(2,3)
fraction2 = MyFraction(1,5)
print(fraction1 < fraction2) # False
크기 비교는 순서대로 이루어지므로 numerator
를 먼저 비교하고 다음으로 denominator
를 비교한다. 그런데, 사실 분수의 크기 비교는 이렇게 이루어지지 않는다. 따라서 새롭게 정의할 필요가 있는데, magic method인 __le__
, __lt__
, __gt__
, __ge__
함수를 구현하면 된다.
dataclass
를 변하지 않도록 만들 수 있는데, 이를 위해서는 @dataclass
의 frozen
을 True
로 설정해야 한다. 이렇게하면 dataclass
의 어떤 값도 변경되지 않는다. 이는 frozen
을 True
로 설정하면 해당 dataclass
가 hashable이 되기 때문에 hash값이 바뀌는 일이 생기면 이러한 연산에 대해 거부할 수 있게 된다. 즉, __hash__
magic method가 자동으로 설정되어 hashable된다는 것이다.
from dataclasses import dataclass
@dataclass(frozen=True)
class MyFraction:
numerator: int = 0
denominator: int = 1
fraction1 = MyFraction(2,3)
print(fraction1.numerator) #
print(fraction1.denominator) #
fraction1.denominator = 0 # dataclasses.FrozenInstanceError: cannot assign to field 'denominator'
다만, frozen
은 맴버 변수의 값이 변하는 것을 막을 뿐이지 맴버 변수의 맴버 변수는 바꿀 수 있다. 또한, list나 dict의 경우는 데이터를 추가할 수 있다. 즉, frozen
은 dataclass
의 맴버 변수 자체가 다른 것으로 변경되는 것을 막는 것이지, 맴버 변수의 내부 동작까지 막을 수 있는 것은 아니다.
from dataclasses import dataclass
@dataclass
class Desc:
description: str
@dataclass(frozen=True)
class MyFraction:
numerator: int
denominator: int
desc: Desc
author: list[str]
fraction1 = MyFraction(2,3, Desc("hello"), ["gyu", "gyu2"])
fraction1.desc.description = "bye"
fraction1.author.append("gyu3")
print(fraction1.numerator) # 2
print(fraction1.denominator) # 3
print(fraction1.desc.description) # bye
print(fraction1.author) # ['gyu', 'gyu2', 'gyu3']
fraction1.desc = Desc("no") # dataclasses.FrozenInstanceError: cannot assign to field 'desc'
다음의 예제를 보면 fraction1.desc.description
을 변경하는 것과 fraction1.author
에 새로운 값을 추가하는 것은 문제가 없이 동작한다. 그러나 fraction1.desc = Desc("no")
을 보면 맴버 변수에 새로운 값(인스턴스)를 할당하는 것은 안된다.