~22 min · dataclass, frozen, kw_only, field, post_init
Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
약속 — 30 줄 대신 3 줄
대부분 단순 클래스 — __init__, __repr__, __eq__ 필요. 손으로 짜면 반복적. @dataclass 가 클래스 본문의 타입 힌트 읽고 다 생성. class Point: x: int; y: int — 선언 3 줄, 장황한 손으로 쓴 버전과 정확히 같이 동작.
실제로 중요한 데코레이터 파라미터
frozen=True — 인스턴스 immutable (생성 후 속성 할당 X, __hash__ 자동). kw_only=True — 모든 필드 생성 시 keyword-only (positional 인자 X). order=True — 정렬 dunder 생성. slots=True (3.10+) — 메모리 효율 위한 __slots__. 디폴트 (@dataclass 괄호 없이) 가 일회용 데이터 컨테이너에 보통 맞음.
field() — 단순 형태로 표현 못 하는 경우
필드 디폴트 값은 디폴트 인자처럼 작동 — 같은 mutable-default 함정. tags: list[str] = [] 가 인스턴스 간 list 공유. 해결 — tags: list[str] = field(default_factory=list). field() 는 또 __repr__ 에서 필드 skip, __eq__ 에서 제외, init=False 도 가능.
__post_init__ — 자동 init 후 추가 setup
dataclass 가 __init__ 생성, 근데 가끔 추가 setup 로직 필요 — 검증, 파생 필드 등. __post_init__ 정의하면 자동 생성 __init__ 끝에 실행. "반지름에서 면적 계산" 또는 "음수면 raise" 는 여기.
Pythonic Way: 값 모양 클래스의 디폴트로 @dataclass 손에 닿아 — 요청 body, 설정 레코드, 파싱된 이벤트. "이 필드 들고있기" 너머 의미있는 동작 있으면 일반 class. tuple 의미 (immutable, 인덱싱, unpacking) 가 특히 원하면 typing.NamedTuple.
Code
손으로 쓴 클래스 vs dataclass — 같은 동작·python
from dataclasses import dataclass
# 손으로
class PointManual:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"PointManual(x={self.x}, y={self.y})"
def __eq__(self, other):
return isinstance(other, PointManual) and self.x == other.x and self.y == other.y
# Dataclass
@dataclass
class Point:
x: int
y: int
p1 = Point(3, 4)
p2 = Point(3, 4)
print(p1) # Point(x=3, y=4)
print(p1 == p2) # True
frozen / kw_only / order — 유용한 플래그·python
from dataclasses import dataclass
@dataclass(frozen=True)
class Coordinate:
lat: float
lon: float
c = Coordinate(37.5, 127.0)
try:
c.lat = 40.0 # frozen — 할당 raise
except Exception as e:
print(type(e).__name__, e)
# frozen 은 무료로 __hash__ 도
print({c, Coordinate(37.5, 127.0)}) # 한 원소 set (dedup)
@dataclass(kw_only=True)
class Config:
host: str
port: int = 8000
debug: bool = False
# 키워드 인자 필수
cfg = Config(host="localhost", debug=True)
try:
Config("localhost", 9000) # positional 거부
except TypeError as e:
print(e)
field — 디폴트 / factory / 제외·python
from dataclasses import dataclass, field
@dataclass
class User:
name: str
tags: list[str] = field(default_factory=list) # 인스턴스마다 새 list
password_hash: str = field(repr=False, default="") # __repr__ 에서 제외
id: int = field(init=False, default=0) # __init__ 인자 X
u1 = User("alice")
u1.tags.append("admin")
u2 = User("bob")
print(u1.tags) # ['admin']
print(u2.tags) # [] — 독립
print(u1) # User(name='alice', tags=['admin'], id=0) — password_hash 숨김
__post_init__ — 파생 필드 + 검증·python
from dataclasses import dataclass, field
@dataclass
class Range:
start: int
end: int
length: int = field(init=False) # __init__ X, 계산
def __post_init__(self):
if self.end < self.start:
raise ValueError("end >= start 여야")
self.length = self.end - self.start
r = Range(start=10, end=25)
print(r) # Range(start=10, end=25, length=15)
try:
Range(start=10, end=5)
except ValueError as e:
print(e)
dataclass 안 쓸 때 — 진짜 동작 있을 때·python
# dataclass 는 *값* 용 (자동 __init__/__repr__/__eq__ 가진 데이터).
# 풍부한 동작 있으면 일반 클래스.
class Connection: # 동작 많음 — 좋은 dataclass 후보 X
def __init__(self, host):
self.host = host
self._socket = None
def connect(self):
# 복잡한 setup
self._socket = f"<socket to {self.host}>"
def send(self, data):
if self._socket is None:
raise RuntimeError("연결 X")
return f"sent {data!r} via {self._socket}"
# 이거 dataclass 로 하려면 post_init + field(init=False) 잔뜩 — 직접 쓰는 게 명확.
@dataclassOrder — 필드 id: str, items: list[str], total: float = 0.0. items 에 field(default_factory=list). __post_init__ 에서 가격 lookup dict 로 total 재계산 (힌트 — dataclass 의 클래스 레벨 상수로 가격 dict — PRICES: ClassVar[dict] = {...}). 그 다음 두 번째 변형 FrozenOrder(frozen=True, kw_only=True) — 불변성 + set 가능 보여.
Progress
Progress is local-only — sign in to sync across devices.