frozen dataclass: 불변 데이터 클래스
Python @dataclass(frozen=True)의 동작 방식, 자동 __hash__ 생성, dict/set 키로의 활용, __post_init__에서의 값 설정, 상속 시 주의사항을 설명합니다.
지난 글에서 @dataclass로 보일러플레이트 코드를 줄이는 방법을 살펴보았습니다. frozen=True 옵션을 추가하면 인스턴스 생성 후 속성을 변경할 수 없는 불변(immutable) 데이터 클래스가 됩니다. 불변성은 스레드 안전성 확보, 딕셔너리 키 사용, 버그 방지 등 여러 이점을 제공합니다.
frozen=True 기본
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
p.x = 9.0 # FrozenInstanceError: cannot assign to field 'x'
frozen=True는 __setattr__와 __delattr__를 오버라이드해서 모든 속성 변경 시도를 차단합니다.
가변 vs 불변 비교
핵심 이점: hash 자동 생성
기본 @dataclass에서 eq=True(기본값)이면 __hash__는 None으로 설정되어 딕셔너리 키로 쓸 수 없습니다. frozen=True를 추가하면 필드 값으로 __hash__가 자동 생성됩니다.
@dataclass(frozen=True)
class RGB:
r: int
g: int
b: int
color_names = {RGB(255, 0, 0): "red", RGB(0, 255, 0): "green"}
color_names[RGB(255, 0, 0)] # "red"
활용 패턴
replace()로 특정 필드만 바꾼 새 인스턴스를 만들 수 있습니다. 원본은 변경되지 않습니다. 이는 함수형 프로그래밍의 불변 데이터 변환 패턴과 일치합니다.
__post_init__에서 속성 설정
frozen=True에서도 __post_init__은 호출됩니다. 단, __init__이 완료된 뒤이므로 일반 속성 할당이 차단됩니다. 파생 필드를 계산해서 저장하려면 object.__setattr__()을 직접 사용합니다.
@dataclass(frozen=True)
class Circle:
radius: float
area: float = 0.0 # 초기화용 더미 값
def __post_init__(self):
# self.area = ... → FrozenInstanceError
object.__setattr__(self, 'area', 3.14 * self.radius ** 2)
또는 field(init=False)와 __post_init__을 조합합니다.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Circle:
radius: float
area: float = field(init=False)
def __post_init__(self):
object.__setattr__(self, 'area', 3.14 * self.radius ** 2)
상속 시 주의사항
frozen과 non-frozen dataclass를 혼합 상속하면 TypeError가 발생합니다.
@dataclass
class Base:
x: int
@dataclass(frozen=True)
class Child(Base): # TypeError!
y: int
frozen 계층을 일관되게 유지하거나, 모두 frozen 또는 모두 non-frozen으로 통일해야 합니다.
frozen dataclass와 가변 필드
frozen=True여도 필드 자체는 불변이지만, 필드가 참조하는 객체는 변경 가능합니다.
@dataclass(frozen=True)
class Config:
tags: list # 리스트 자체는 변경 가능!
c = Config(tags=["a", "b"])
c.tags.append("c") # 가능! c.tags = [...] 는 불가
진정한 불변성이 필요하면 가변 필드에 tuple이나 frozenset을 씁니다.
지난 글: @dataclass: 보일러플레이트 없는 데이터 클래스
다음 글: slots: 메모리 효율적인 클래스
읽어주셔서 감사합니다. 😊