데이터 클래스는 말그대로 데이터를 다루는데 유리한 클래스입니다. 이 기능은 파이썬 3.7부터 지원됩니다. 이 비슷한 게 스칼라에 case class입니다. 이런 류의 코드는 초기화가 간단합니다. 또, 매번 작성하게 되는 코드들이 내장 되어 있습니다.

기존의 클래스 방식

dataclass가 등장하기 전 어떻게 class에 데이터를 담아두었는지 확인해 봅니다.

from datetime import date

class Book:
    def __init__(
        self, id: int, title: str, isbn: date):
        self.id = id
        self.title = title
        self.isbn = isbn
        

위를 보면 id, title, isbn이 3번씩 반복되는 것을 알 수 있습니다. 이런 코드를 보일러 플레이드(boiler-plate)라고 합니다. 자바에서 getter, setter를 무의하게 남발하는 코드랑 모양이 좀 비슷합니다. 파이썬에서는 getter, setter없이 바로 인가해서 코드가 짧긴하지만, self.title = title같은 식으로 기입해줘야하고, 그리고 이런류의 class를 작성하다보면 상당 부분이런 부분이 반복되는것 확인할수 있습니다.

또한 위 클래스를 인스턴스로 출력해 보면 출력 결과에 필드값이 나타나지 않기에 보통 출력이 되도록 repr() 또는 str() 메서드를 추가하여 출력되도록 해줍니다.

from datetime import date

class Book:
    def __init__(
        self, id: int, title: str, isbn: int, ) -> None:
        self.id = id
        self.title = title
        self.isbn = isbn
        

    def __repr__(self):
        return (
            self.__class__.__qualtitle__ + f"(id={self.id!r}, title={self.title!r}, "
            f"isbn={self.isbn!r})")
>>> book1 = Book(id=1, title="Hello Python", isbn=978839422832 )
>>> book1
Book(id=1, title='Hello Python', isbn=978839422832)

데이터클래스 작성하기

데이터 클래스 모듈은 위와 같이 데이터를 담아두기 위한 클래스를 매우 적은 양의 코드로 짧게 작성하게 해줍니다.

from dataclasses import dataclass
from datetime import date

@dataclass
class Book:
    id: int
    title: str
    isbn: date

이렇게 내장함수로 추가되는 함수들은 다음과 같습니다.

  • init - 초기화

  • repr - text 표시

  • eq - 같은지 비교

  • order - 순서 비교, <,>, <=,>= 연산들이 자동으로 구현

  • frozen

이렇게만 작성하면 위의 작성했던 메소드들이 전부 자동으로 생성이 됩니다. 추가로 만약 데이터의 불변성이 보장되어야하는 경우라면 frozen 옵션을 사용하면 됩니다. 이 frozen option은 마치 scala에서 immutable로 생성되는것과 비슷한동작을 합니다.

@dataclass(frozen=True)

데이터 대소 비교 및 정렬

데이터 클래스의 인스턴스 간에 대소비교를 하려고 하면 에러가 발생하는데 order 옵션을 주면 대소 비교를 사용할 수 있습니다

from dataclasses import dataclass
from datetime import date

@dataclass(order=True)
class Book:
    id: int
    title: str
    isbn: date

>>> book1 = Book(id=1, title="Hello Python", isbn=978839422832)
>>> book2 = Book(id=2, title="Hello CPP", isbn=978839422844)
>>> book1 < book2
True
>>> book1 > book2
False
>>> sorted([book2, book1])
[Book(id=1, title='Hello Python', isbn=978839422832), Book(id=2, title='Hello CPP', isbn=978839422844)]

이 옵션의 경우 tuple순서대로 비교를 해서 우선순위를 판단합니다.

셋트나 딕셔너리 사용하기

데이터 클래스의 인스턴스는 기본적으로 hashable 하지 않기에 세트(set)의 값이나 사전(dictionary)의 키로 사용을 할 수가 없습니다.

>>> set([book1, book2])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'Book'

데이터클래스의 인스턴스를 hashable하게 만들고 싶다면 unsafe_hash 옵션을 사용하면 됩니다.

from dataclasses import dataclass
from datetime import date

@dataclass(unsafe_hash=True)
class Book:
    id: int
    title: str
    isbn: date

set명령어를 이용해 중복 데이터를 제거할 수 있습니다. 아래 명령중 book1과 book3은 중복이고, book2와 book4는 중복이니 각각 하나씩만 남게 됩니다.

>>> book1 = Book(id=1, title="Hello Python", isbn=978839422832)
>>> book2 = Book(id=2, title="Hello CPP", isbn=978839422844)
>>> book3 = Book(id=1, title="Hello Python", isbn=978839422832)
>>> book4 = Book(id=2, title="Hello CPP", isbn=978839422844)
>>> set([book1, book2, book3, book4]){Book(id=2, title='Hello CPP', isbn=datetime.978839422844), Book(id=1, title='Hello Python', isbn=datetime.978839422832)}

데이터클래스 사용시 주의사항

데이터 클래스를 사용할 때 흔히 나오는 실수는 list와 같은 가변 데이터 타입의 필드에 기본값을 할당해 줄 때 발생한다.

from dataclasses import dataclass
from datetime import date
from typing import List

@dataclass(unsafe_hash=True)
class Book:
    id: int
    title: str
    isbn: date
    friends: List[int] = []

필드의 기본값은 인스턴스 간에 공유가 되기 때문에 기본값 할당이 허용되지 않습니다 이럴 때는 filed 함수의 default_factory 옵션을 사용해서 매번 새로운 리스트가 생성될 수 있도록 해줘야 합니다.

from dataclasses import dataclass, field
from datetime import date
from typing import List

@dataclass(unsafe_hash=True)
class Book:
    id: int
    title: str
    isbn: date
    friends: List[int] = field(default_factory=list)

이상으로 dataclass에 대해서 정리해보았습니다.

dataclass는 어찌보면 반복적인 코드에 대해서 잘 사용하면 유용한 방법이고 편리함을 제공해주는 방법입니다.