study/Python 🌼

[Python] dataclass 모듈 사용법

서나쓰 2021. 5. 28. 15:11
728x90

파이썬으로 코딩을 하면서 데이터를 담아두기 위해 여러가지 방법을 사용한다.

리스트, 튜블, 딕셔너리, 네임드 튜플, 세트, 프로즌 세트 등등..

반면 클래스를 통해 데이터를 담아두면 type-safe해지기 때문에 프로그램 실행 중 오류가 발생할 확률이 적어진다는 장점이 있다.

파이썬 3.7에서 dataclass라는 모듈이 표준 라이브러리에 추가되었다. 이를 알아보고자 한다.

공식문서

dataclasses - Data Classes - Python 3.9.5 documentation

 

dataclasses — Data Classes — Python 3.9.5 documentation

This module provides a decorator and functions for automatically adding generated special methods such as __init__() and __repr__() to user-defined classes. It was originally described in PEP 557. The member variables to use in these generated methods are

docs.python.org

기존의 클래스 방식

dataclass가 등장하기 전 어떻게 데이터를 담아두기 위해 클래스를 작성하였는지 보자.

from datetime import date

class User:
    def __init__(
        self, id: int, name: str, birthdate: date, admin: bool = False):
        self.id = id
        self.name = name
        self.birthdate = birthdate
        self.admin = admin

위를 보면 id, name, birthdate, admin이 3번씩 반복되는 것을 알 수 있다. 이런 코드를 보일러 플레이드(boiler-plate)라고 한다. 필드 개수가 많은 클래스라면 이런 코드를 작성하는 것이 매우 복잡하고 어렵고, 오타라도 나면 버그로 이어질 수 있다.

또한 위 클래스를 인스턴스로 출력해 보면 출력 결과에 필드값이 나타나지를 않는다.

>>> user = User(id=1, name="Steve Jobs", birthdate=date(1955, 2, 24))
>>> user
<__main__.User object at 0x105558100>

출력이 되도록 __reper__() 메서드를 추가하여 출력되도록 바꿔본다.

from datetime import date

class User:
    def __init__(
        self, id: int, name: str, birthdate: date, admin: bool = False
    ) -> None:
        self.id = id
        self.name = name
        self.birthdate = birthdate
        self.admin = admin

    def __repr__(self):
        return (
            self.__class__.__qualname__ + f"(id={self.id!r}, name={self.name!r}, "
            f"birthdate={self.birthdate!r}, admin={self.admin!r})")
>>> user1 = User(id=1, name="Steve Jobs", birthdate=date(1955, 2, 24))
>>> user1
User(id=1, name='Steve Jobs', birthdate=datetime.date(1955, 2, 24), admin=False)

두 개의 인스턴스간의 필드값이 모두 같을 때 동등한 인스턴스로 취급하고 십다면 __eq__() 메서드를 구현해 줘야 한다.

from datetime import date

class User:
    def __init__(
        self, id: int, name: str, birthdate: date, admin: bool = False
    ) -> None:
        self.id = id
        self.name = name
        self.birthdate = birthdate
        self.admin = admin

    def __repr__(self):
        return (
            self.__class__.__qualname__ + f"(id={self.id!r}, name={self.name!r}, "
            f"birthdate={self.birthdate!r}, admin={self.admin!r})"
        )

    def __eq__(self, other):
        if other.__class__ is self.__class__:
            return (self.id, self.name, self.birthdate, self.admin) == (
                other.id,
                other.name,
                other.birthdate,
                other.admin,
            )
        return NotImplemented
>>> user1 = User(id=1, name="Steve Jobs", birthdate=date(1955, 2, 24))
>>> user2 = User(id=1, name="Steve Jobs", birthdate=date(1955, 2, 24))
>>> user1 == user2
True

데이터클래스 작성하기

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

from dataclasses import dataclass
from datetime import date

@dataclass
class User:
    id: int
    name: str
    birthdate: date
    admin: bool = False

이렇게만 작성하면 위의 작성했던 메소드들이 전부 자동으로 생성이 된다.

만약 데이터의 불변성이 보장되어야하는 경우라면 frozen 옵션을 사용하면 된다.

@dataclass(frozen=True)

데이터 대소 비교 및 정렬

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

from dataclasses import dataclass
from datetime import date

@dataclass(order=True)
class User:
    id: int
    name: str
    birthdate: date
    admin: bool = False
>>> user1 = User(id=1, name="Steve Jobs", birthdate=date(1955, 2, 24))
>>> user2 = User(id=2, name="Bill Gates", birthdate=date(1955, 10, 28))
>>> user1 < user2
True
>>> user1 > user2
False
>>> sorted([user2, user1])
[User(id=1, name='Steve Jobs', birthdate=datetime.date(1955, 2, 24), admin=False), User(id=2, name='Bill Gates', birthdate=datetime.date(1955, 10, 28), admin=False)]

셋트나 딕셔너리 사용하기

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

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

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

from dataclasses import dataclass
from datetime import date

@dataclass(unsafe_hash=True)
class User:
    id: int
    name: str
    birthdate: date
    admin: bool = False

세트를 이용해 중복 데이터를 제거할 수 있다.

>>> user1 = User(id=1, name="Steve Jobs", birthdate=date(1955, 2, 24))
>>> user2 = User(id=2, name="Bill Gates", birthdate=date(1955, 10, 28))
>>> user3 = User(id=1, name="Steve Jobs", birthdate=date(1955, 2, 24))
>>> user4 = User(id=2, name="Bill Gates", birthdate=date(1955, 10, 28))
>>> set([user1, user2, user3, user4]){User(id=2, name='Bill Gates', birthdate=datetime.date(1955, 10, 28), admin=False), User(id=1, name='Steve Jobs', birthdate=datetime.date(1955, 2, 24), admin=False)}

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

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

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

@dataclass(unsafe_hash=True)
class User:
    id: int
    name: str
    birthdate: date
    admin: bool = False
    friends: List[int] = []

필드의 기본값은 인스턴스 간에 공유가 되기 때문에 기본값 할당이 허용되지 않는다.

이럴 때는 filed 함수의 default_factory 옵션을 사용해서 매번 새로운 리스트가 생성될 수 있도록 해줘야 한다.

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

@dataclass(unsafe_hash=True)
class User:
    id: int
    name: str
    birthdate: date
    admin: bool = False
    friends: List[int] = field(default_factory=list)
728x90

'study > Python 🌼' 카테고리의 다른 글

[Python] 월 단위의 날짜 차이 계산  (0) 2021.07.05
[Python] Mac SSLCertVerificationError  (0) 2021.06.30
[Python] SQLAlcehmy  (0) 2021.05.28
[Python] 암호화를 하는 bcrypt  (0) 2021.05.28
[Python] classmethod vs staticmethod  (0) 2021.05.28