dataclass
정의와 용법날짜: 2024-12-10
dataclass
는 Python 3.7에 도입된 데코레이터로, 데이터 중심의 클래스를 더 간결하고 직관적으로 작성할 수 있도록 도와줍니다. 기본적으로 데이터 저장용 클래스를 만들 때 반복적으로 작성해야 하는 코드를 자동으로 생성해줍니다.
__init__
) 자동 생성__repr__
, __eq__
, __hash__
등의 메서드 자동 생성field()
및 __post_init__
으로 다양한 동작을 설정할 수 있습니다.dataclass
정의from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
city: str = "Seoul" # 기본값
# 인스턴스 생성
p1 = Person(name="Alice", age=30)
print(p1) # 출력: Person(name='Alice', age=30, city='Seoul')
__init__
메서드를 자동으로 생성.__repr__
메서드 자동 생성.__eq__
메서드 생성.p2 = Person(name="Alice", age=30)
print(p1 == p2) # True (name과 age가 같기 때문)
dataclass
는 field()
를 사용하여 필드의 동작을 세밀하게 제어할 수 있습니다.
from dataclasses import dataclass, field
@dataclass
class Product:
id: int
name: str
price: float = field(default=0.0) # 기본값 설정
tags: list = field(default_factory=list) # 가변 객체를 위한 기본값
p = Product(id=1, name="Laptop")
print(p) # 출력: Product(id=1, name='Laptop', price=0.0, tags=[])
__post_init__
메서드생성자가 실행된 후 추가 로직을 수행할 수 있습니다.
@dataclass
class Rectangle:
width: int
height: int
area: int = field(init=False) # 생성자에서 직접 초기화하지 않음
def __post_init__(self):
self.area = self.width * self.height
r = Rectangle(4, 5)
print(r.area) # 출력: 20
frozen=True
옵션을 사용하면 불변 객체를 생성할 수 있습니다.frozen=True
로 설정하면 객체가 해시 가능(hash 자동 생성)하며, 딕셔너리 키나 집합 원소로 사용할 수 있습니다.@dataclass(frozen=True)
class ImmutablePoint:
x: int
y: int
p = ImmutablePoint(10, 20)
# p.x = 30 # TypeError: cannot assign to field 'x'
default_factory
를 사용해야 합니다. 그렇지 않으면 모든 인스턴스가 동일한 객체를 참조합니다.
@dataclass
class MyClass:
items: list = [] # 잘못된 사용
dataclass
는 상속을 지원하지만, 상위 클래스의 동작이 자동으로 상속되지는 않으므로 주의해야 합니다.from dataclasses import dataclass, field, asdict, astuple
from typing import List, Dict
@dataclass(frozen=True) # 객체를 불변으로 설정
class Product:
# 필드 정의 (타입 힌트와 기본값 포함)
id: int # 반드시 초기화해야 하는 필드
name: str # 반드시 초기화해야 하는 필드
price: float = 0.0 # 기본값 제공
tags: List[str] = field(default_factory=list) # 가변 객체를 위한 기본값
metadata: Dict[str, str] = field(default_factory=dict) # 가변 객체
discount_rate: float = field(init=False) # 초기화에서 제외된 필드
total_stock: int = field(default=100, metadata={"unit": "items"}) # 메타데이터 추가
def __post_init__(self):
# `__post_init__`에서 추가 초기화 로직 및 validation 수행
object.__setattr__(self, 'discount_rate', 0.1 if self.price > 100 else 0.0)
if self.price < 0:
raise ValueError("Price must be non-negative")
if not (0 <= self.discount_rate <= 1):
raise ValueError("Discount must be between 0 and 1")
def apply_discount(self) -> float:
# 객체의 값을 활용한 메서드
return self.price * (1 - self.discount_rate)
# 객체 생성 (초기화)
product = Product(id=1, name="Laptop", price=1500.0, tags=["Electronics", "Portable"])
# 출력 및 사용 예시
print(product) # __repr__ 자동 생성
# 출력: Product(id=1, name='Laptop', price=1500.0, tags=['Electronics', 'Portable'], metadata={}, discount_rate=0.1, total_stock=100)
# 데이터 구조 변환
print(asdict(product)) # 딕셔너리로 변환
# 출력: {'id': 1, 'name': 'Laptop', 'price': 1500.0, 'tags': ['Electronics', 'Portable'], 'metadata': {}, 'total_stock': 100, 'discount_rate': 0.1}
print(astuple(product)) # 튜플로 변환
# 출력: (1, 'Laptop', 1500.0, ['Electronics', 'Portable'], {}, 0.1, 100)
# 메서드 사용
print(f"Discounted price: {product.apply_discount()}") # 메서드 실행
# 출력: Discounted price: 1350.0
# 불변 객체 검증
try:
product.price = 2000 # 수정 시도 (frozen=True로 인해 예외 발생)
except AttributeError as e:
print(e)
# 출력: cannot assign to field 'price'