← Back to Blog

[Python] @dataclass

computer science > programming

2026-07-063 min read

#computer-science #programming #python #dataclass

@dataclass란?

@dataclass는 Python에서 데이터를 담는 class를 간단하게 작성할 수 있게 해주는 decorator이다.

일반적으로 class를 만들면 __init__, __repr__, __eq__ 같은 method를 직접 작성해야 한다. 하지만 @dataclass를 사용하면 type annotation을 기반으로 이런 method를 자동으로 만들어준다.

예를 들어 다음과 같은 class가 있다고 하자.

class User:
    def __init__(self, id: int, name: str, email: str):
        self.id = id
        self.name = name
        self.email = email

    def __repr__(self):
        return f"User(id={self.id!r}, name={self.name!r}, email={self.email!r})"

@dataclass를 사용하면 다음처럼 줄일 수 있다.

from dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str
    email: str

사용은 일반 class와 같다.

user = User(id=1, name="Alice", email="alice@example.com")
print(user)

출력은 다음과 비슷하다.

User(id=1, name='Alice', email='alice@example.com')

자동으로 만들어지는 method

@dataclass는 기본적으로 다음 method를 자동 생성한다.

method역할
__init__field를 받아 instance 초기화
__repr__instance를 읽기 좋은 문자열로 표현
__eq__두 instance의 field 값을 비교

예를 들어 다음 두 객체는 같은 값으로 비교된다.

from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int


p1 = Point(1, 2)
p2 = Point(1, 2)

print(p1 == p2)  # True

일반 class였다면 __eq__를 직접 구현하지 않는 이상 두 객체는 서로 다른 객체로 비교된다. dataclass는 field 값 기준 비교를 자동으로 제공한다.


기본값 설정

field에 기본값을 줄 수 있다.

from dataclasses import dataclass


@dataclass
class ServerConfig:
    host: str = "localhost"
    port: int = 8000
    debug: bool = False

이제 인자를 생략할 수 있다.

config = ServerConfig()
print(config)
ServerConfig(host='localhost', port=8000, debug=False)

일부 값만 바꿀 수도 있다.

config = ServerConfig(port=3000)

주의할 점은 기본값이 없는 field가 기본값이 있는 field보다 먼저 와야 한다는 것이다.

@dataclass
class User:
    id: int
    name: str = "unknown"

다음처럼 기본값이 있는 field 뒤에 기본값이 없는 field를 두면 error가 발생한다.

@dataclass
class User:
    name: str = "unknown"
    id: int  # error

mutable default와 default_factory

list, dict 같은 mutable object를 기본값으로 직접 넣으면 안 된다.

from dataclasses import dataclass


@dataclass
class Team:
    members: list[str] = []  # bad

이런 방식은 여러 instance가 같은 list를 공유할 수 있으므로 위험하다. dataclass에서는 이런 실수를 막기 위해 mutable default를 제한한다.

대신 field(default_factory=...)를 사용한다.

from dataclasses import dataclass, field


@dataclass
class Team:
    name: str
    members: list[str] = field(default_factory=list)

이제 instance마다 새로운 list가 만들어진다.

team_a = Team("A")
team_b = Team("B")

team_a.members.append("Alice")

print(team_a.members)  # ['Alice']
print(team_b.members)  # []

dict도 마찬가지로 처리한다.

from dataclasses import dataclass, field


@dataclass
class Cache:
    values: dict[str, int] = field(default_factory=dict)

field 옵션

field()를 사용하면 field의 동작을 세밀하게 제어할 수 있다.

from dataclasses import dataclass, field


@dataclass
class User:
    id: int
    password: str = field(repr=False)

repr=False를 사용하면 print(user)를 했을 때 해당 field가 출력되지 않는다.

user = User(id=1, password="secret")
print(user)
User(id=1)

비교에서 제외할 수도 있다.

from dataclasses import dataclass, field


@dataclass
class Item:
    name: str
    cache_key: str = field(compare=False)

compare=False인 field는 == 비교에 사용되지 않는다.


frozen dataclass

frozen=True를 사용하면 instance를 불변 객체처럼 만들 수 있다.

from dataclasses import dataclass


@dataclass(frozen=True)
class Coordinate:
    x: int
    y: int

값을 만든 뒤 field를 바꾸려고 하면 error가 발생한다.

point = Coordinate(1, 2)
point.x = 10  # error

설정값, 좌표, 식별자처럼 생성 후 바뀌면 안 되는 데이터에 유용하다.

@dataclass(frozen=True)
class DatabaseConfig:
    host: str
    port: int
    database: str

다만 완전한 깊은 불변성을 보장하는 것은 아니다. field 안에 mutable object가 들어가면 그 내부 object는 여전히 변경될 수 있다.


order 옵션

order=True를 사용하면 크기 비교 method가 생성된다.

from dataclasses import dataclass


@dataclass(order=True)
class Student:
    score: int
    name: str

이제 정렬이 가능하다.

students = [
    Student(90, "Alice"),
    Student(80, "Bob"),
    Student(95, "Charlie"),
]

students.sort()
print(students)

정렬은 field 선언 순서를 기준으로 한다. 위 예시는 score를 먼저 비교하고, score가 같으면 name을 비교한다.

특정 field를 비교에서 제외하려면 compare=False를 사용한다.

from dataclasses import dataclass, field


@dataclass(order=True)
class Student:
    score: int
    name: str = field(compare=False)

post_init

@dataclass가 생성한 __init__ 이후에 추가 초기화가 필요하면 __post_init__을 사용한다.

from dataclasses import dataclass


@dataclass
class Rectangle:
    width: int
    height: int
    area: int = 0

    def __post_init__(self):
        self.area = self.width * self.height

사용 예시는 다음과 같다.

rect = Rectangle(3, 4)
print(rect.area)  # 12

입력값 검증에도 사용할 수 있다.

from dataclasses import dataclass


@dataclass
class Product:
    name: str
    price: int

    def __post_init__(self):
        if self.price < 0:
            raise ValueError("price must be non-negative")

asdict와 astuple

dataclass instance를 dictionary나 tuple로 바꿀 수 있다.

from dataclasses import asdict, astuple, dataclass


@dataclass
class User:
    id: int
    name: str


user = User(1, "Alice")

print(asdict(user))
print(astuple(user))

출력은 다음과 같다.

{'id': 1, 'name': 'Alice'}
(1, 'Alice')

API response, config 변환, logging 등에 사용할 수 있다.

다만 JSON 문자열로 바로 바뀌는 것은 아니다. JSON으로 변환하려면 json.dumps()와 함께 사용한다.

import json
from dataclasses import asdict, dataclass


@dataclass
class User:
    id: int
    name: str


user = User(1, "Alice")
payload = json.dumps(asdict(user))

dataclass가 적합한 경우

@dataclass는 데이터를 담는 목적의 class에 잘 맞는다.

상황설명
DTO계층 사이에서 데이터를 전달
config설정값을 묶어서 관리
value object좌표, 색상, 범위 같은 값 표현
parser resultparsing 결과를 구조화
test fixture테스트용 데이터를 간단히 생성

예를 들어 API 요청 결과를 담는 class로 사용할 수 있다.

from dataclasses import dataclass


@dataclass
class LoginResult:
    user_id: int
    access_token: str
    expires_in: int

dataclass가 애매한 경우

@dataclass는 모든 class를 대체하는 도구가 아니다.

다음 상황에서는 일반 class가 더 적절할 수 있다.

상황이유
복잡한 invariant생성과 변경 규칙을 강하게 통제해야 함
behavior 중심 객체data보다 method가 핵심
상속 구조가 복잡함dataclass field 순서와 init 규칙이 복잡해짐
validation이 많음Pydantic 같은 library가 더 적합할 수 있음
private state가 중요함단순 data container와 맞지 않을 수 있음

@dataclass는 data container를 간결하게 만드는 도구로 보는 것이 좋다.


정리

@dataclass는 Python에서 데이터를 담는 class를 간단하게 만들기 위한 decorator이다.

핵심은 다음과 같다.

  1. type annotation을 기반으로 __init__, __repr__, __eq__를 자동 생성한다.
  2. 기본값을 field에 직접 줄 수 있다.
  3. mutable default는 field(default_factory=...)를 사용한다.
  4. frozen=True로 불변 객체처럼 만들 수 있다.
  5. order=True로 정렬 가능한 객체를 만들 수 있다.
  6. 추가 초기화와 검증은 __post_init__에서 처리한다.
  7. asdict, astuple로 다른 자료구조로 변환할 수 있다.

데이터를 묶기 위해 반복적인 class 코드를 작성하고 있다면 @dataclass를 먼저 고려해볼 만하다.