파이썬

파이썬 dataclass란? (@dataclass, 데이터클래스)

cocojen 2022. 6. 6. 20:10

dataclass란?

파이썬 3.7부터 dataclass라는 모듈이 추가되었다.

말 그대로 데이터를 담는 클래스이고, import 후에 @dataclass 라는 데코레이터를 사용하면 된다.

기존의 방식으로 클래스를 사용하는 것과 비교했을 때, dataclass가 가지는 몇 가지 장점들이 있다.

이번 포스트에서는 dataclass의 사용법과 편리한 점들을 정리해보겠다.

 

dataclass 사용법

- 아이스크림 정보를 담는 클래스를 예시로 기존의 클래스와 데이터클래스를 비교해보자.

그냥 클래스를 사용해서 아이스크림을 만들 때:

import random
import string


def generate_id() -> str:
    return "".join(random.choices(string.ascii_uppercase, k=12))
    // 아래에서 사용예정


class Icecream:
    def __init__(self, name: str, flavor: str):
        self.name = name
        self.flavor = flavor


def main() -> None:
    icecream = Icecream(name="togegther", flavor="vanilla")
    print(icecream)


if __name__ == "__main__":
    main()

generate_id 라는 랜덤한 아이디를 만드는 메소드가 있고,  name과 flavor를 가지는 아이스크림 클래스가 있고, main함수에서는 Icecream을 만들고 print찍는다.

main 함수에서 만든 아이스크림 인스턴스를 프린트로 찍어줄 때 <__main__.Icecream object at 0x10234bcd0> 와 같이 메모리 주소가 찍혀서 보기 좋지 않다.

str 메소드를 작성해주면 print 했을 때 클래스를 프린트했을 때 찍히는 값을 설정해줄 수 있으니 str 메소드를 작성해주자.

 

import random
import string


def generate_id() -> str:
    return "".join(random.choices(string.ascii_uppercase, k=12))


class Icecream:
    def __init__(self, name: str, flavor: str):
        self.name = name
        self.flavor = flavor

    def __str__(self) -> str:
        return f"{self.name} - {self.flavor} flavor"


def main() -> None:
    icecream = Icecream(name="together", flavor="vanilla")
    print(icecream)


if __name__ == "__main__":
    main()

 

str 메소드를 위와 같이 작성해주니까 프린트문이 together - vanilla flavor 로 보기 좋게 나온다.

 

데이터 클래스를 사용해서 아이스크림을 만들 때:

위에서 만든 클래스와 똑같은 정보를 가지는 아이스크림 데이터클래스를 만들어보자.

import random
import string
from dataclasses import dataclass


def generate_id() -> str:
    return "".join(random.choices(string.ascii_uppercase, k=12))


@dataclass
class Icecream:
    name: str
    flavor: str


def main() -> None:
    icecream = Icecream(name="together", flavor="vanilla")
    print(icecream)


if __name__ == "__main__":
    main()

한 눈에 봐도 코드의 길이가 짧아졌다. instance variable을 위와 같이 작성해주면 끝이다. 

기존의 클래스와 비교해서 데이터 클래스가 더 빠르고 쉬운 이유는

 

1) 데이터클래스가 이니셜라이저를 자동으로 만들어주고

 

2) __repr__ 메소드도 자동으로 생성해주기 때문에 위에서와 같이 프린트 찍을 때 인스턴스의 값을 보기 위해 따로 __str__ 메소드를

작성해주거나 하지 않아도 되고, ( 따로 메소드를 작성해주지 않아도 icecream을 프린트하면 Icecream(name='together', flavor='vanilla') 와 같이 출력됨 )

 

3) 위와 같이 name: str 같은 형식으로 타입을 쉽게 제공할 수 있기 떄문이다.

 

데이터 클래스의 간단한 사용법을 알아보았으니, 데이터클래스를 사용했을 때 얻을 수 있는 장점을 알아보자.

dataclass 의 여러가지 기능

1. 디폴트 값 설정하기

: 아래와 같이 in_stock 이라는 인스턴스 변수에 boolean 값으로 True를 주면 이니셜라이저에 따로 명시하지 않아도 디폴트 값이 True로 설정된다.

@dataclass
class Icecream:
    name: str
    flavor: str
    in_stock: bool = True
Icecream(name='together', flavor='vanilla', in_stock=True)

 

조금 더 심화해서, 아이스크림이 발매되는 사이즈들을 넣을 list와 아이디에 디폴트 값을 주어보자.

여기서 주의할 점은 email_address: list = [] 와 같은 식으로 줄 수 없다는 점이다.

list는 mutable하기 때문에 모든 인스턴스들이 필드의 기본 값을 공유하기 때문에, 허용되지 않는다.

따라서 field를 import 하고 default_factory 를 사용해주어야 한다.

데이터클래스가 클래스를 생성할 때, default_factory=list 로 적어놓은 함수(타입아님)를 call 해서 매번 새로운 리스트를 생성해준다.

 

@dataclass
class Icecream:
    name: str
    flavor: str
    in_stock: bool = True
    sizes: list = field(default_factory=list)
    id: str = field(default_factory=generate_id)

디폴트값을 준 것이니, 당연히 클래스를 만들 때 값을 넣어주면 그 값을 가진 클래스가 생성된다.

    icecream = Icecream(name="together", flavor="vanilla", id="my_id")

아래는 print(icecream) 의 결과 :

 

Icecream(name='together', flavor='vanilla', in_stock=True, sizes=[], id='my_id')

 

2. Init 옵션 주기

여기에서 위와같이 id를 따로 생성해줄 수 없게 만들고 싶을 때는, init 옵션을 주면 된다.

@dataclass
class Icecream:
    name: str
    flavor: str
    in_stock: bool = True
    sizes: List[str] = field(default_factory=list)
    id: str = field(init=False, default_factory=generate_id)


def main() -> None:
    icecream = Icecream(name="together", flavor="vanilla", id="my_id")
    print(icecream)

이렇게 init=False 옵션을 주고 이니셜라이저에 id값을 넣어주면 아래와 같은 에러가 난다.

TypeError: __init__() got an unexpected keyword argument 'id'

 

3. post_init 메서드 사용해서 필드 생성하기

만약에 위 Icecream 을 찾기 쉽게 유니크한 아이디와 제품명으로 이루어진 search_string 이라는 필드를 추가하고자 하면 어떻게 해야할까? 아이디는 인스턴스가 생성될 때 생성되기 때문에 동시에 값을 넣어줄 수 없다. 인스턴스가 생성된 후에 id값과 name 값이 생겨야만 만들 수 있는 필드이다. 이럴 때는 말그대로 post_init , 즉 이니셜라이즈 후의 라는 메소드를 사용하면 된다.

@dataclass
class Icecream:
    name: str
    flavor: str
    in_stock: bool = True
    sizes: List[str] = field(default_factory=list)
    id: str = field(init=False, default_factory=generate_id)
    search_str: str = field(init=False)

    def __post_init__(self) -> None:
        self.search_str = f"{self.name} - {self.id}"
Icecream(name='together', flavor='vanilla', in_stock=True, sizes=[], id='WGSJNSLLIKRX', search_str='together - WGSJNSLLIKRX')

search_str 필드의 값도 잘 생성된 것을 확인할 수 있다.

 

이 외에도 데이터클래스의 장점은 아주 많다.

더 많은 정보는 아래의 유투브 링크를 추천한다.

내가 매우 좋아하는 파이썬 유투버이고, 이 블로그 또한 그의 영상을 참고하여 (예시는 내가 만들었다) 정리한 것이다.

 

 

참고 : https://www.youtube.com/watch?v=CvQ7e6yUtnw