모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

37강 / 전체 40강

데이터클래스 — 현대적인 클래스 작성법

9분 읽기 조회 0

@dataclass로 보일러플레이트를 제거하고 field(), frozen, order 옵션으로 현대적인 클래스를 설계한다.

🎯 이 강의에서 배우는 것

클래스를 만들 때마다 __init__, __repr__, __eq__를 반복해서 작성하는 것은 지루하고 실수를 유발합니다. Python 3.7에서 도입된 @dataclass 데코레이터는 이 보일러플레이트를 자동으로 생성합니다. 단순한 문법 편의가 아닙니다. 타입 힌트 기반 선언, field() 함수, frozen 객체로 현대적인 파이썬 코드의 표준이 되었습니다.

📋 보일러플레이트 문제 — @dataclass 이전과 이후

# Before: 10개 필드 클래스의 반복 작업
class PersonOld:
    def __init__(self, name: str, age: int, email: str, city: str):
        self.name = name
        self.age = age
        self.email = email
        self.city = city

    def __repr__(self):
        return f"PersonOld(name={self.name!r}, age={self.age!r}, email={self.email!r}, city={self.city!r})"

    def __eq__(self, other):
        if not isinstance(other, PersonOld):
            return NotImplemented
        return (self.name, self.age, self.email, self.city) == (other.name, other.age, other.email, other.city)

# After: @dataclass로 위와 완전히 동일한 기능
from dataclasses import dataclass, field, fields

@dataclass
class Person:
    name: str
    age: int
    email: str
    city: str

p1 = Person("홍길동", 30, "hong@example.com", "서울")
p2 = Person("홍길동", 30, "hong@example.com", "서울")
p3 = Person("이순신", 45, "lee@example.com", "부산")

print(p1)           # Person(name='홍길동', age=30, email='hong@example.com', city='서울')
print(p1 == p2)     # True — 자동 생성된 __eq__
print(p1 == p3)     # False
print(p1.name)      # 홍길동

🔧 field() 함수 — 고급 필드 제어

from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime

@dataclass
class BlogPost:
    title: str
    author: str
    content: str

    # 기본값
    published: bool = False

    # 가변 기본값 안전 처리 — 절대 [] 직접 사용 금지!
    # tags: List[str] = []  # 이렇게 하면 DataclassTypeError!
    tags: List[str] = field(default_factory=list)

    # repr에서 제외 — 긴 콘텐츠나 비밀 정보
    view_count: int = field(default=0, repr=False)

    # 비교에서 제외 — ID는 같음 비교에 포함 안 함
    post_id: Optional[str] = field(default=None, compare=False)

    # init에서 제외 — __post_init__에서 계산
    created_at: datetime = field(default_factory=datetime.now, init=False)
    word_count: int = field(default=0, init=False, repr=False)

    def __post_init__(self):
        # @dataclass가 __init__ 호출 직후 자동으로 __post_init__ 실행
        self.word_count = len(self.content.split())
        if not self.title:
            raise ValueError("제목은 비워둘 수 없습니다")

post = BlogPost(
    title="파이썬 데이터클래스 가이드",
    author="홍길동",
    content="파이썬 3.7에서 도입된 데이터클래스는 보일러플레이트를 제거합니다.",
    tags=["python", "dataclass"],
)
print(post)
print(f"단어 수: {post.word_count}")
print(f"생성 시각: {post.created_at}")

🧊 frozen 데이터클래스 — 불변 객체

from dataclasses import dataclass
from typing import Tuple

@dataclass(frozen=True)     # 불변 → hash 가능 → set/dict 키 가능
class Point:
    x: float
    y: float

    def distance_to(self, other: "Point") -> float:
        return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5

p1 = Point(0, 0)
p2 = Point(3, 4)

# 수정 불가
try:
    p1.x = 10   # FrozenInstanceError!
except Exception as e:
    print(f"{type(e).__name__}: {e}")

# frozen → hash 자동 생성 → set과 dict 키 사용 가능
print(hash(p1))
points = {p1, p2, Point(0, 0)}   # p1과 Point(0,0)은 같음
print(len(points))   # 2 (중복 제거)

# dict 키로 사용
distances = {Point(0, 0): "원점", Point(1, 0): "x축"}
print(distances[Point(0, 0)])   # 원점

print(p1.distance_to(p2))   # 5.0

# @dataclass(order=True): 비교 연산자 자동 생성
@dataclass(order=True)
class Version:
    major: int
    minor: int
    patch: int

    def __str__(self):
        return f"{self.major}.{self.minor}.{self.patch}"

versions = [Version(2, 0, 0), Version(1, 9, 3), Version(1, 10, 0)]
print(sorted(versions))   # 필드 순서대로 비교: major → minor → patch
# 1.9.3, 1.10.0, 2.0.0

📊 dataclass vs namedtuple vs TypedDict

특성@dataclassnamedtupleTypedDict
가변성기본 가변, frozen 선택불변 (tuple)가변
상속일반 클래스 상속제한적딕셔너리
메서드 추가✅ 자유롭게✅ 가능❌ 불가
해시 가능frozen=True 시✅ 항상❌
튜플 언패킹❌✅❌
런타임 타입 검사❌ (힌트만)❌❌
주요 용도도메인 객체레코드, DB 행JSON 구조 타입
from collections import namedtuple
from typing import TypedDict

# namedtuple: 불변, 튜플 언패킹 가능
Color = namedtuple("Color", ["r", "g", "b"])
red = Color(255, 0, 0)
print(red)           # Color(r=255, g=0, b=0)
r, g, b = red        # 튜플 언패킹
print(r, g, b)       # 255 0 0
print(red[0])        # 255 (인덱싱도 가능)

# TypedDict: 딕셔너리에 타입 힌트만 추가
class UserData(TypedDict):
    name: str
    age: int
    email: str

user: UserData = {"name": "홍길동", "age": 30, "email": "hong@example.com"}
# 런타임에는 그냥 dict — 타입 체커(mypy)를 위한 힌트
print(user["name"])   # 홍길동

# 선택: 도메인 모델(행동 포함) → @dataclass
#        불변 레코드 → namedtuple 또는 frozen dataclass
#        외부 API JSON → TypedDict

⚠️ 자주 하는 실수

  • 가변 기본값을 직접 선언: tags: list = []는 모든 인스턴스가 같은 리스트를 공유합니다. 반드시 field(default_factory=list)를 사용하세요. @dataclass는 이를 감지해 ValueError를 발생시킵니다.
  • 기본값 있는 필드 뒤에 기본값 없는 필드 선언: @dataclass는 일반 함수의 매개변수 규칙과 동일합니다. 기본값이 있는 필드는 기본값이 없는 필드보다 뒤에 와야 합니다.
  • frozen 아닌데 dict 키로 사용: @dataclass는 기본으로 __hash__ = None이어서 set/dict 키로 쓸 수 없습니다. 불변 객체가 필요하면 @dataclass(frozen=True)를 사용하세요.

📝 정리 및 다음 강의 예고

  • @dataclass: __init__, __repr__, __eq__ 자동 생성. 필드는 타입 힌트로 선언.
  • field(): 기본값, 가변 기본값(default_factory), repr 제외, 비교 제외, init 제외 등 세밀한 제어.
  • __post_init__: 초기화 후 검증, 계산 필드 설정.
  • frozen=True: 불변 + 해시 가능. order=True: 비교 연산자 자동 생성.

다음 강의: 예외 처리 — 파이썬의 오류 처리 철학. try/except/else/finally의 정확한 흐름, 커스텀 예외 설계, EAFP vs LBYL 철학을 배웁니다.

관련 주제

  • @dataclass 데코레이터
  • field() 함수
  • frozen 데이터클래스
  • __post_init__
  • InitVar
  • dataclass vs namedtuple 비교
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 파이썬 기초 40강 — 처음 배우는 프로그래밍
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

파이썬 기초 40강 — 처음 배우는 프로그래밍

이 강의가 속한 시리즈는 총 40강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...