본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev20

dataclass와 namedtuple, 값 객체를 만들 때 뭐가 다를까

  • #API Design
  • #Engineering Note
  • #Python

문제 발생

좌표를 표현하는 작은 값 객체가 필요했는데, namedtuple로 만들지 dataclass로 만들지 결정하지 못해 팀 안에서 코드 스타일이 뒤섞였습니다.

원인 분석

둘 다 "필드 몇 개를 가진 작은 값 객체"를 __init__/__repr__/__eq__ 같은 보일러플레이트 없이 만들 수 있게 해주지만, 설계 목적이 다릅니다.

namedtuple(collections.namedtuple 또는 typing.NamedTuple)은 이름 그대로 튜플입니다.

  • 튜플이므로 기본적으로 불변(immutable)입니다 — 필드를 나중에 바꿀 수 없습니다.
  • 튜플처럼 인덱스로도 접근 가능하고(point[0]), 언패킹도 됩니다(x, y = point).
  • 튜플이 필요한 모든 곳(딕셔너리 키, 셋의 원소 등)에 그대로 쓸 수 있습니다 — 해시 가능합니다.

dataclass(dataclasses.dataclass)는 일반 클래스입니다.

  • 기본적으로 가변(mutable)입니다 — frozen=True 옵션을 줘야 불변이 됩니다.
  • 메서드를 자유롭게 추가할 수 있고, 상속 구조를 만들기 쉽습니다.
  • 필드에 기본값, field(default_factory=...) 같은 고급 기능을 쓸 수 있습니다.
  • 기본적으로 해시 가능하지 않습니다(가변 객체는 보통 해시 불가능해야 안전합니다) — 필요하면 frozen=True와 함께 eq=True를 조합해 해시 가능하게 만들 수 있습니다.

해결 방안

  1. 정말 단순한 불변 값 튜플이 필요하고, 튜플처럼 인덱스/언패킹으로도 쓰고 싶다면 NamedTuple을 씁니다.
from typing import NamedTuple

class Point(NamedTuple):
    x: int
    y: int
  1. 메서드가 필요하거나, 필드가 나중에 바뀌어야 하거나, 상속 구조가 필요하다면 dataclass를 씁니다.
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

    def distance_from_origin(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5
  1. 값 객체가 절대 바뀌면 안 된다는 걸 명확히 하고 싶다면 dataclass(frozen=True)dataclass도 불변으로 만들 수 있습니다 — "불변이 필요하다"는 이유만으로 NamedTuple을 선택할 필요는 없습니다.
  2. 튜플 언패킹/인덱스 접근이 필요 없다면 dataclass가 더 명시적이고 확장하기 쉬운 선택인 경우가 많습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.