@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
| 특성 | @dataclass | namedtuple | TypedDict |
|---|---|---|---|
| 가변성 | 기본 가변, 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
불러오는 중...
