Python

Python dataclass 사용하기

순돌아범 2022. 12. 29. 13:02

python 3.7에 추가된 dataclass 는 데이터 저장을 위해 사용하는 클래스를 더욱 편리하게 구현이 가능합니다.

또한 인스턴스를 print 했을 때 값을 확인할 수 있으며, 딕셔너리나 튜플 타입으로 변환이 가능하기 때문에 유용하게 사용할 수 있습니다.

 

기존 방식으로 class 생성

 

기존 방식으로 클래스를 만들면 아래 소스처럼 __init__() 메서드를 추가해줘야 합니다.

before.py

class Person:
    def __init__(self, name: str, gender: str, age: int):
        self.name = name
        self.gender = gender
        self.age = age

    def say_hello(self):
        print("Hello, " + self.name)


gildong = Person('홍길동', 'M', 28)
print(gildong)
gildong.say_hello()

print("나이는 :" + str(gildong.age))
gildong.age = 30
print("나이는 :" + str(gildong.age))

gildong.job = "학생"
print("직업은 : " + gildong.job)

실행 결과

$ python before.py
<__main__.Person object at 0x000001B10AE97548>
Hello, 홍길동
나이는 :28
나이는 :30
직업은 : 학생

 

 

dataclass 이용하여 클래스 생성

 

dataclass 를 이용하여 아래처럼 데코레이터만 붙여주면 소스를 간소화 시킬 수 있습니다.

init=True 옵션을 주면 멤버변수를 생성자로 입력받을 수 있습니다.

 

after.py

from dataclasses import dataclass

@dataclass(init=True)
class Person:
    name: str
    gender: str
    age: int
    
    def say_hello(self):
        print("Hello, " + self.name)


gildong = Person('홍길동', 'M', 28)
print(gildong)
print(gildong.__repr__)
gildong.say_hello()

print("나이는 :" + str(gildong.age))
gildong.age = 30
print("나이는 :" + str(gildong.age))

gildong.job = "학생"
print("직업은 : " + gildong.job)

실행 결과

$ python after.py
Person(name='홍길동', gender='M', age=28)
<bound method __create_fn__.<locals>.__repr__ of Person(name='홍길동', gender='M', age=28)>
Hello, 홍길동
나이는 :28
나이는 :30
직업은 : 학생

gildong이라는 인스턴스를 출력했을 때 값이 출력되는데 이는 __repr__ 내부 메소드가 정의되어 있기 때문입니다.

인스턴스 내부 값을 바로 조회해 볼 수 있다는 점 하나만으로도 큰 장점이 될 수 있을 것 같습니다.

 

 

frozen 옵션

 

frozen 옵션을 지정하면 멤버변수를 임의로 추가하거나 변경이 불가능하도록 설정할 수 있습니다.

after2.py

from dataclasses import dataclass

@dataclass(init=True, frozen=True)
class Person:
    name: str
    gender: str
    age: int
    
    def say_hello(self):
        print("Hello, " + self.name)


gildong = Person('홍길동', 'M', 28)
gildong.say_hello()

print("나이는 :" + str(gildong.age))
gildong.age = 30  # <-- 오류 발생
print("나이는 :" + str(gildong.age))

gildong.job = "학생"  # <-- 오류 발생
print("직업은 : " + gildong.job)

실행 결과

$ python after2.py
Hello, 홍길동
나이는 :28
Traceback (most recent call last):
  File "after2.py", line 17, in <module>
    gildong.age = 30  # <-- 오류 발생
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'age'

인스턴스 생성 시 할당한 값을 변경하거나 정의되지 않은 멤버변수 추가 시 오류가 발생하도록 frozen 옵션을 지정할 수 있습니다.

 

 

 

post_init

 

멤버변수 초기값 지정 및 인스턴스 생성 후 자동 실행 메서드 추가가 가능합니다.

after3.py

from dataclasses import dataclass

@dataclass()
class Person:
    name: str
    age: int
    gender: str = 'M'
    
    
    def __post_init__(self):
        """ __init()__ 이후 처리될 부분"""

        if self.age < 18:
            if self.gender == 'M':
                print(self.name + "군 환영합니다.")
            else:
                print(self.name + "양 환영합니다.")
        else:
            print(self.name + "씨 환영합니다.")
        
    def say_hello(self):
        print("Hello, " + self.name)


gildong = Person(name = '홍길동', age=28 )
gildong.say_hello()
print(gildong)

gilsun = Person(name = '홍길순', age=14, gender='F')
print(gilsun)

실행 결과

$ python after3.py
홍길동씨 환영합니다.
Hello, 홍길동
Person(name='홍길동', age=28, gender='M')
홍길순양 환영합니다.
Person(name='홍길순', age=14, gender='F')

초기값을 지정하는 멤버변수는 가장 마지막에 정의 해야 합니다.

post_init 메소드는 인스턴스 생성 이후 처리해야 할 부분을 지정할 수 있습니다.

위 예제 처럼 인스턴스 생성 시 입력받은 값을 비교하여 나이/성별에 따라 다르게 표현이 가능합니다.

list, dictionary 같은 mutable 타입의 초기값을 지정하려면 default_factory를 이용하여야 합니다.

 

after3-1.py

from dataclasses import dataclass, field
from typing import List

@dataclass()
class Person:
    name: str
    age: int
    gender: str = 'M'
    subject: List[str] = field(default_factory=list)
    
    def say_hello(self):
        print("Hello, " + self.name)


gildong = Person('홍길동', 28, 'M', ['korean','math','english'])
gilsun = Person(name = '홍길순', age=14, gender='F')
print(gildong)
print(gilsun)

실행 결과

$ python after3-1.py
Person(name='홍길동', age=28, gender='M', subject=['korean', 'math', 'english'])
Person(name='홍길순', age=14, gender='F', subject=[])

 

 

Type 변환

 

dataclass 로 생성된 클래스는 딕셔너리나 튜플로 바로 변환이 가능합니다.

after4.py

from dataclasses import asdict, astuple, dataclass

@dataclass(init=True)
class Person:
    name: str
    gender: str
    age: int
    
    def say_hello(self):
        print("Hello, " + self.name)


gildong = Person('홍길동', 'M', 28)
gildong.say_hello()

print("나이는 :" + str(gildong.age))
print(gildong)
print(asdict(gildong))
print(astuple(gildong))

실행 결과

$ python after4.py 
Hello, 홍길동
나이는 :28
Person(name='홍길동', gender='M', age=28)
{'name': '홍길동', 'gender': 'M', 'age': 28}
('홍길동', 'M', 28)

위 실행 결과 처럼 asdict, astuple 함수를 이용하여 바로 타입을 변환할 수 있기 때문에 복잡한 과정을 거쳐 원하는 형식으로 가공하는 과정을 생략할 수 있습니다.

 

 

멤버변수 타입은 힌트로만 사용됨

 

after5.py

from dataclasses import asdict, astuple, dataclass

@dataclass(init=True)
class Person:
    name: str
    gender: str
    age: int
    
    def say_hello(self):
        print("Hello, " + self.name)


gildong = Person('홍길동', 'M', '28')
print(gildong)
gilsun = Person('홍길순',123,dict())
print(gilsun)

실행 결과

$ python after5.py
Person(name='홍길동', gender='M', age='28')
Person(name='홍길순', gender=123, age={})

 

 

dataclass 매개 변수

 

위에서 init, frozen 2가지 사용에 따른 차이점을 알아보았는데, 위 2가지 외에 다른 매개변수도 존재합니다.

매개변수
default
역할
init
True
__init__ 메서드가 자동으로 생성됨
frozen
False
멤버변수 수정 및 추가 시 예외를 발생시킴
repr
True
__repr__ 메서드가 자동으로 생성됨
어떤 객체인지 문자열로 출력 가능
eq
True
__eq__ 메서드가 자동으로 생성됨
두 객체가 동일한지 판단할 수 있음
order
False
True 일 때 __lt__, __le__, __gt__, __ge__ 메서드가 생성됨
객체의 대소를 판단할 수 있음
unsafe_hash
False
True 일 때 __hash__메서드가 생성됨
인스턴스를 딕셔너리나 set 집합으로 만들고 싶을 때 사용