파이썬(python) dataclass 사용/활용 예제

728x90
  1. 기본 dataclass - Point 클래스
  2. 기본값 설정 - 생성자에서 기본값을 사용하는 방법
  3. field() 함수 활용 - 복잡한 기본값 설정 및 속성 제어
    • default_factory로 가변 객체 기본값 설정
    • repr, compare 등 필드 동작 제어
  4. 초기화 후 처리 - __post_init__와 InitVar 활용법
  5. 불변 객체 생성 - frozen=True 옵션으로 불변 dataclass 생성
  6. 상속 구현 - dataclass 간 상속 관계 설정
  7. 클래스 변수 사용 - ClassVar를 통한 인스턴스 간 공유 변수 설정
  8. JSON 변환 - dataclass 객체의 직렬화/역직렬화
  9. 동적 dataclass 생성 - 런타임에 dataclass 동적 생성하기
  10. 복잡한 실제 사례 - 주문 시스템 구현 예제

dataclass를 사용하면 다음과 같은 장점이 있음.

  • __init__, __repr__, __eq__ 등의 메서드를 자동으로 생성
  • 타입 힌팅을 통한 더 명확한 코드 작성
  • 상속, 불변성 등 객체지향 디자인 패턴 적용 용이
  • 데이터 처리와 직렬화/역직렬화 간소화
from dataclasses import dataclass, field, InitVar, make_dataclass, asdict, astuple, replace
from typing import List, Dict, Optional, ClassVar, Any
import json
from datetime import datetime


# 1. 기본 dataclass 예제
@dataclass
class Point:
    x: int
    y: int

    def __str__(self):
        return f"{self.x}, {self.y}"


# 2. 기본값 설정 예제
@dataclass
class Rectangle:
    width: int
    height: int = 10  # 기본값 설정
    color: str = "white"

    def area(self):
        return self.width * self.height


# 3. field() 함수를 사용한 고급 설정
@dataclass
class Product:
    name: str
    price: float
    # default_factory를 사용하여 가변 기본값 설정
    tags: List[str] = field(default_factory=list)
    # repr=False로 설정하여 repr 출력에서 제외
    internal_id: str = field(default="", repr=False)
    # compare=False로 설정하여 객체 비교에서 제외
    timestamp: datetime = field(
        default_factory=datetime.now, 
        compare=False
    )

    def is_expensive(self):
        return self.price > 100


# 4. 초기화 후 처리 및 InitVar 사용
@dataclass
class Circle:
    radius: float
    diameter: InitVar[Optional[float]] = None  # 초기화에만 사용되는 변수
    area: float = field(init=False)  # 초기화에서 제외

    def __post_init__(self, diameter):
        # diameter가 제공되면 radius 계산
        if diameter is not None:
            self.radius = diameter / 2
        # area 계산
        self.area = 3.14159 * (self.radius ** 2)


# 5. 불변 dataclass (frozen=True)
@dataclass(frozen=True)
class ImmutablePoint:
    x: int
    y: int

    def distance_from_origin(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5


# 6. 상속을 사용한 dataclass
@dataclass
class Person:
    name: str
    age: int

    def is_adult(self):
        return self.age >= 18

@dataclass
class Employee(Person):
    company: str
    salary: float = 0

    def annual_salary(self):
        return self.salary * 12


# 7. ClassVar 사용 예제
@dataclass
class Config:
    # 클래스 변수 (인스턴스마다 다르지 않음)
    VERSION: ClassVar[str] = "1.0.0"
    DEBUG: ClassVar[bool] = False

    # 인스턴스 변수
    user_name: str
    api_key: str


# 8. JSON 변환 예제
@dataclass
class User:
    id: int
    name: str
    email: str
    active: bool = True
    permissions: List[str] = field(default_factory=list)

    def to_json(self):
        return json.dumps(asdict(self))

    @classmethod
    def from_json(cls, json_str):
        return cls(**json.loads(json_str))


# 9. 동적으로 dataclass 생성
Fields = [
    ('name', str),
    ('age', int, field(default=0)),
    ('email', str, field(default=''))
]
# 동적으로 dataclass 생성
DynamicPerson = make_dataclass('DynamicPerson', Fields)


# 10. 복잡한 예제: 주문 시스템
@dataclass
class OrderItem:
    product_id: int
    name: str
    price: float
    quantity: int = 1

    @property
    def total(self):
        return self.price * self.quantity

@dataclass
class Order:
    id: int
    customer_name: str
    items: List[OrderItem] = field(default_factory=list)
    created_at: datetime = field(default_factory=datetime.now)
    processed: bool = False

    @property
    def total_amount(self):
        return sum(item.total for item in self.items)

    def add_item(self, item):
        self.items.append(item)

    def to_dict(self):
        return {
            'id': self.id,
            'customer_name': self.customer_name,
            'items': [asdict(item) for item in self.items],
            'created_at': self.created_at.isoformat(),
            'processed': self.processed,
            'total_amount': self.total_amount
        }


# 테스트 및 사용 예제
def run_examples():
    print("\n===== 1. 기본 예제 =====")
    p1 = Point(100, 200)
    p2 = Point(100, 200)
    p3 = Point(1, 2)
    print(f"p1: {p1}")
    print(f"x={p1.x}, y={p1.y}")
    print(f"p1 == p2: {p1 == p2}")  # True (자동 생성된 __eq__)
    print(f"p1 != p3: {p1 != p3}")  # True

    print("\n===== 2. 기본값 설정 =====")
    r1 = Rectangle(5)  # height는 기본값 사용
    r2 = Rectangle(5, 20, "blue")
    print(f"r1: {r1}, 넓이: {r1.area()}")
    print(f"r2: {r2}, 넓이: {r2.area()}")

    print("\n===== 3. field() 함수 사용 =====")
    prod1 = Product("노트북", 1200.0, ["전자제품", "컴퓨터"])
    prod2 = Product("마우스", 25.0)
    prod2.tags.append("주변기기")
    print(f"prod1: {prod1}")  # internal_id는 repr에서 제외됨
    print(f"prod2: {prod2}")
    print(f"prod1은 비싼가?: {prod1.is_expensive()}")
    print(f"prod2는 비싼가?: {prod2.is_expensive()}")
    # 동일한 tags 리스트를 공유하지 않음 (default_factory 덕분)
    print(f"prod1.tags != prod2.tags: {prod1.tags != prod2.tags}")

    print("\n===== 4. 초기화 후 처리 =====")
    c1 = Circle(5.0)
    c2 = Circle(radius=0, diameter=10.0)  # diameter로부터 radius 계산
    print(f"c1: {c1}")
    print(f"c2: {c2}")

    print("\n===== 5. 불변 dataclass =====")
    ip = ImmutablePoint(3, 4)
    print(f"ip: {ip}")
    print(f"원점까지의 거리: {ip.distance_from_origin()}")
    try:
        ip.x = 10  # 에러 발생: frozen=True이므로 수정 불가
    except Exception as e:
        print(f"불변 객체 수정 시도 시 에러: {type(e).__name__}")

    # replace() 함수로 불변 객체의 수정된 복사본 생성
    ip2 = replace(ip, x=10)
    print(f"replace로 새 객체 생성: {ip2}")

    print("\n===== 6. 상속 =====")
    emp = Employee("홍길동", 30, "ABC주식회사", 5000000)
    print(f"직원: {emp}")
    print(f"성인 여부: {emp.is_adult()}")
    print(f"연봉: {emp.annual_salary()}원")

    print("\n===== 7. ClassVar 사용 =====")
    config = Config("user123", "api_key_123")
    print(f"config: {config}")
    print(f"버전: {Config.VERSION}")
    print(f"디버그 모드: {Config.DEBUG}")

    print("\n===== 8. JSON 변환 =====")
    user = User(1, "김철수", "kim@example.com", permissions=["read", "write"])
    user_json = user.to_json()
    print(f"JSON: {user_json}")
    user2 = User.from_json(user_json)
    print(f"JSON에서 복원: {user2}")

    print("\n===== 9. 동적 dataclass =====")
    dp = DynamicPerson("이영희", 25, "lee@example.com")
    print(f"동적 생성 객체: {dp}")

    print("\n===== 10. 복잡한 예제: 주문 시스템 =====")
    order = Order(1, "박지성")
    order.add_item(OrderItem(101, "키보드", 50000, 1))
    order.add_item(OrderItem(102, "마우스", 30000, 2))

    print(f"주문: {order}")
    print(f"총 금액: {order.total_amount}원")
    print(f"주문 정보(dict): {order.to_dict()}")

    # asdict, astuple 함수 사용
    print("\n===== 추가: asdict, astuple 함수 =====")
    point_dict = asdict(p1)
    point_tuple = astuple(p1)
    print(f"asdict(): {point_dict}")
    print(f"astuple(): {point_tuple}")


if __name__ == "__main__":
    run_examples()
728x90