Python dataclass 사용하기
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 집합으로 만들고 싶을 때 사용
|