nomad-coders님의 영상을 보다가 dataclasses라는 유용한 라이브러리를 알게 되어 정리하게 되었다. 정리한 내용은 공식 문서 (3.11)를 참고하여 작성하였다. 추가로 객체 데이터를 다룰 때 유용하게 사용하고 있는 property도 함께 소개하였다.
dataclasses 개념
class Person:
def __init__(self, pid, name, age):
self.pid = pid
self.name = name
self.age = age
class내에서 __init__을 사용해 필요한 데이터를 저장해두는 아주 전형적인 코드이다. 하지만 받아야 하는 데이터가 많을 경우, 동일한 코드를 반복적으로 작성해야 하는 번거로움이 생긴다.
이럴 때 dataclasses를 사용해 간결하게 작성할 수 있다.
from dataclasses import dataclass
@dataclass
class Person:
pid: int
name: str
age: int
대신 Python 3.7부터 표준 라이브러리로 추가되었기 때문에 버전에 유의해야 한다.
dataclasses 사용법
from dataclasses import dataclass
@dataclass(
init=True,
repr=True,
eq=True,
order=False,
unsafe_hash=False,
frozen=False,
match_args=True,
kw_only=False,
slots=False,
weakref_slot=False,
)
class Data:
...
dataclass의 기본 설정 값이다.
- init: True면, __init__ 메서드를 자동 생성한다.
- repr: True면, __repr__ 메서드를 자동 생성한다. i.e. Person(id=6, name='DeneV')
- eq: True면, __eq__ 메서드를 생성해 데이터 값들이 일치하는지 확인한다.
- frozen: True면, 데이터를 불변값으로 설정한다. 내부 데이터를 변경하려고 시도하면 TypeError가 발생한다.
- kw_only: True면, __init__ 메서드는 파라미터로 keyword 인자만 받는다. 그렇지 않으면 TypeError가 발생한다. (3.10에 추가)
frozen 예시
@dataclass(frozen=True)
class Person:
pid: int
me = Person(3)
me.pid = 10 # FrozenInstanceError
kw_only 예시
@dataclass(kw_only=True)
class Person:
pid: int
me = Person(3) # TypeError
me = Person(pid=3) # 성공
field
field는 추가적으로 정보를 설정하기 위해 사용할 수 있다.
from dataclasses import field
field(
default=MISSING,
default_factory=MISSING,
init=True,
repr=True,
hash=None,
compare=True,
metadata=None,
kw_only=MISSING,
)
- default: 데이터의 기본값을 지정하는데 사용한다.
- default_factory: 데이터의 기본값이 필요할 때, 파라미터를 받지 않는 호출 가능 객체로 설정한다. 쉽게 말해, 기본 값을 반환하는 함수 또는 클래스로 선언되어야 한다. default와 default_factory는 같이 사용될 수 없다.
- init: False면, __init__ 메서드의 파라미터에서 제외된다.
- repr: False면, __repr__ 메서드의 출력 결과에서 제외된다.
default_factory 예시
from dataclasses import dataclass, field
@dataclass
class Container:
cid: int
items: list[int] = field(default_factory=list)
list는 가변 객체이기 때문에 default를 list로 설정하면 참조로 인한 문제가 발생할 수 있다. 따라서 list를 기본 값으로 설정할 때는 반드시 default_factory를 사용해야 한다. 위 예시의 경우, Container가 생성될 때마다 items의 값은 빈 list가 된다.
__post_init__
post-init은 __init__이 실행된 후, 이어서 초기화를 수행하는 메서드이다.
@dataclass
class Circle:
radius: int
circumference: float = field(init=False)
def __post_init__(self):
pi = 3.141592
self.circumference = self.radius * 2 * pi
a = Circle(5)
print(a.circumference) # 6.283184
먼저 radius가 __init__에서 초기화된 후, __post_init__을 통해 circumference가 초기화되었다.
타입 변환
from dataclasses import dataclass, asdict, astuple
@dataclass
class Person:
pid: int
name: str
me = Person(3, "DeneV")
me_tuple = astuple(me)
me_dict = asdict(me)
print(me_tuple) # (3, 'DeneV')
print(me_dict) # {'pid': 3, 'name': 'DeneV'}
astuple과 asdict를 통해 dataclass 객체를 각각 tuple과 dict으로 변환할 수 있다.
property
property 데코레이터는 getter와 setter를 간결하게 작성할 수 있도록 도와준다. getter는 객체 내부의 데이터를 외부에서 접근할 수 있도록 도와주는 메서드이며, setter는 외부에서 객체 내부의 데이터를 수정할 수 있도록 도와주는 메서드이다. getter와 setter를 사용하는 것이 'Pythonic(파이썬스러운)' 코드인지에 대해서는 의견이 나뉘지만, 데이터의 접근·수정 조건이 까다로운 경우라면 불가피하게 getter와 setter를 선언해 주어야 한다.
@dataclass
class Product:
__pid: int
name: str
def get_pid(self):
"""getter"""
return f"P{self.__pid}_{self.name}"
def set_pid(self, pid):
"""setter"""
if isinstance(pid, int):
if 0 < pid < 10:
self.__pid = pid
return None
raise TypeError
book = Product(1, "Book")
book.set_pid(7)
book_pid = get_pid()
print(book_pid) # P7_Book
book.__pid # AttributeError
book.set_pid(70) # TypeError
getter는 pid와 name을 조합해 값을 반환하고, setter는 pid가 특정 조건을 만족하는지 검사한 후 등록한다. 하지만 이렇게 작성할 경우, 객체 외부에서 데이터에 접근할 때마다 get_pid와 같이 명시적으로 함수를 호출해야 하기 때문에 코드의 가독성이 떨어진다. 이럴 때 사용할 수 있는 것이 property 데코레이터이다.
@dataclass
class Product:
__pid: int
name: str
@property
def pid(self):
"""getter"""
if self.__pid is not None:
return f"P{self.__pid}_{self.name}"
@pid.setter
def pid(self, new_pid):
"""setter"""
if isinstance(new_pid, int):
if 0 < new_pid < 10:
self.__pid = new_pid
return None
raise TypeError
@pid.deleter
def pid(self):
"""deleter"""
self.__pid = None
book = Product(1, "Book")
book.pid = 7
print(book.pid) # P7_Book
del book.pid
print(book.pid) # None
@property는 getter 메서드를 설정하는데 사용된다. getter 메서드의 이름을 활용해 @'Getter'.setter를 지정하면 setter가 된다. getter와 setter의 이름은 데이터의 변수명과 관계없이 사용할 수 있다. 이렇게 작성하면 객체 외부에서 마치 멤버 변수에 직접 접근하는 것처럼 눈속임하여 사용할 수 있다.
추가로 deleter는 del 키워드를 통해 값을 삭제할 때 호출된다. 위 예시의 경우, pid 값을 삭제하려고 하면 None으로 초기화되도록 작성하였다.