Python @dataclassとは?クラスを短く書く方法を解説

Pythonの@dataclassは、データを持つクラスを短く書くためのデコレータです。

__init__・__repr__・__eq__を自動で作ってくれます。


@dataclassの基本的な使い方

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int

u = User("佐藤", 28)

print(u)
print(u.name, u.age)
print(User("佐藤", 28) == User("佐藤", 28))

実行結果:

User(name='佐藤', age=28)
佐藤 28
True

3行書いただけで、初期化・表示・比較がすべて動いています。


手で書くとどうなるか

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

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

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return (self.name, self.age) == (other.name, other.age)

同じことをするのに12行かかります。しかもフィールドを1つ足すたびに3か所を直す必要があります。

@dataclassなら1行足すだけです。修正漏れによるバグが起きません。


何が自動生成されるのか

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int

print("__init__:", "__init__" in User.__dict__)
print("__repr__:", "__repr__" in User.__dict__)
print("__eq__  :", "__eq__" in User.__dict__)
print("__hash__:", User.__hash__)

実行結果:

__init__: True
__repr__: True
__eq__  : True
__hash__: None

__hash__がNoneになっている点に注目してください。__eq__を定義するとハッシュ不可になるというPythonの規則によるものです。

hash(u)

実行結果:

TypeError: unhashable type: 'User'

辞書のキーや集合の要素にしたいならfrozen=Trueが必要です。「Python dataclassのfrozen=Trueとは?変更できないクラスを作る方法」で解説しています。


型注釈は必須(ただし強制はされない)

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int

u = User(123, "文字列")
print(u)

実行結果:

User(name=123, age='文字列')

型が逆でも通ります。Pythonは実行時に型を検査しないためです。

ただし注釈を書かないとフィールドとして認識されません。

@dataclass
class Bad:
    name = "既定"      # 注釈なし → ただのクラス変数

print(Bad())

実行結果:

Bad()

nameが消えました。: strのような注釈が必須です。型を決めたくないならtyping.Anyを使ってください。

型を実際に検査したいなら、型チェッカー(mypy)を併用します。詳しくは「Python typingモジュールとは?型ヒントの書き方を初心者向けに解説」を参照してください。


既定値は後ろにまとめる

from dataclasses import dataclass

@dataclass
class P:
    name: str
    age: int = 0

print(P("鈴木"))

実行結果:

P(name='鈴木', age=0)

既定値のあるフィールドの後ろに、既定値のないフィールドは置けません。

@dataclass
class Bad:
    a: int = 0
    b: str

実行結果:

TypeError: non-default argument 'b' follows default argument

通常の関数の引数と同じ規則です。


@dataclassのオプション

オプション 既定 効果
init True __init__を作る
repr True __repr__を作る
eq True __eq__を作る
order False 大小比較を作る
frozen False 変更を禁止する
slots False __slots__を作る(3.10以降)
kw_only False キーワード専用にする(3.10以降)
@dataclass(frozen=True, order=True)
class Point:
    x: int
    y: int

__post_init__で初期化後の処理を書く

from dataclasses import dataclass, field

@dataclass
class Rect:
    w: int
    h: int
    area: int = field(init=False)

    def __post_init__(self):
        self.area = self.w * self.h

print(Rect(3, 4))

実行結果:

Rect(w=3, h=4, area=12)

__init__の直後に呼ばれます。計算で求まる値や入力の検証を書く場所です。

def __post_init__(self):
    if self.age < 0:
        raise ValueError(f"年齢が不正です: {self.age}")

field(init=False)を付けたフィールドは__init__の引数から外れます。


他の選択肢との比較

方法 変更 メソッド 向いている場面
@dataclass できる 持てる データ+振る舞い
NamedTuple できない 持てる 軽量・タプルとして扱う
TypedDict できる 持てない JSONを辞書のまま扱う
普通の辞書 できる 持てない 構造が決まらない

迷ったら@dataclassです。あとから振る舞いを足せるうえ、frozen=Trueで不変にもできます。

JSONを辞書のまま扱いたいなら「Python TypedDictとは?辞書のキーと型を定義する方法」、タプルとして扱いたいなら「【Python入門】namedtupleとは?使い方とメリットを初心者向けにわかりやすく解説」を参照してください。


@dataclassのまとめ

  • @dataclassは__init__・__repr__・__eq__を自動生成
  • 型注釈が必須(書かないとフィールドにならない)
  • 型は実行時に検査されない——検査するなら型チェッカー
  • 既定値のあるフィールドは後ろにまとめる
  • __eq__が作られるため既定ではハッシュ不可
  • 初期化後の処理は__post_init__
  • 不変にするならfrozen=True、比較したいならorder=True

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら