基礎

Python dataclassesで楽にクラス定義!実践ガイド

Python

Python dataclassesで
楽にクラス定義!実践ガイド

dataclassesモジュールを使えば、ボイラープレートコードを大幅に削減してクラスを定義できます。

こんな人向けの記事です

  • Pythonのクラス定義を簡潔にしたい人
  • __init__や__repr__を毎回書くのが面倒な人
  • 型ヒントを活用したコードを書きたい人

Step 1dataclassの基本

@dataclassデコレータを使うと、__init____repr____eq__が自動生成されます。

Python
from dataclasses import dataclass

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

# 自動生成された__init__で初期化
user = User(name="田中太郎", age=30, email="tanaka@example.com")
print(user)
# User(name='田中太郎', age=30, email='tanaka@example.com')

# __eq__も自動生成
user2 = User(name="田中太郎", age=30, email="tanaka@example.com")
print(user == user2)  # True
自動生成されるメソッド
__init__(初期化)、__repr__(文字列表現)、__eq__(等価比較)がデフォルトで自動生成されます。

Step 2デフォルト値とfield()

フィールドにデフォルト値を設定できます。ミュータブルなデフォルト値にはfield(default_factory=...)を使います。

Python
from dataclasses import dataclass, field
from typing import List

@dataclass
class Team:
    name: str
    leader: str = "未定"                          # 単純なデフォルト値
    members: List[str] = field(default_factory=list)  # ミュータブルはfieldで
    _score: int = field(default=0, repr=False)     # reprから除外

team1 = Team(name="開発チーム")
team1.members.append("Alice")
print(team1)
# Team(name='開発チーム', leader='未定', members=['Alice'])

team2 = Team(name="営業チーム")
print(team2.members)  # [](team1とは独立)
ミュータブルなデフォルト値に注意
members: list = []と書くとエラーになります。必ずfield(default_factory=list)を使いましょう。

Step 3__post_init__で初期化後処理

__post_init__メソッドで、__init__完了後に追加の初期化処理を実行できます。

Python
from dataclasses import dataclass

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = 0  # __post_init__で計算

    def __post_init__(self):
        self.area = self.width * self.height
        if self.width < 0 or self.height < 0:
            raise ValueError("幅と高さは正の値である必要があります")

rect = Rectangle(width=10, height=5)
print(rect.area)  # 50.0

# バリデーションも可能
# Rectangle(width=-1, height=5)  # ValueError

Step 4frozen(イミュータブル)

frozen=Trueにすると、インスタンス作成後にフィールドを変更できなくなります。

Python
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

p = Point(x=1.0, y=2.0)
print(p)  # Point(x=1.0, y=2.0)

# p.x = 3.0  # FrozenInstanceError!

# frozenなdataclassはハッシュ可能(dictのキーやsetに使える)
points = {Point(0, 0): "原点", Point(1, 1): "右上"}
print(points[Point(0, 0)])  # 原点

Step 5比較と並び替え

order=Trueにすると、比較演算子(<, <=, >, >=)が自動生成されます。

Python
from dataclasses import dataclass

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

students = [
    Student(85, "Alice"),
    Student(92, "Bob"),
    Student(78, "Charlie"),
]

# score → name の順で比較
for s in sorted(students):
    print(f"{s.name}: {s.score}")
# Charlie: 78
# Alice: 85
# Bob: 92

Step 6通常クラスとの比較

dataclassを使わない場合と比べて、コード量が大幅に削減できます。

通常のクラス(ボイラープレートが多い)
class Product:
    def __init__(self, name, price, stock=0):
        self.name = name
        self.price = price
        self.stock = stock

    def __repr__(self):
        return f"Product(name={self.name!r}, price={self.price!r}, stock={self.stock!r})"

    def __eq__(self, other):
        if not isinstance(other, Product):
            return NotImplemented
        return (self.name, self.price, self.stock) == (other.name, other.price, other.stock)
dataclassを使う場合(簡潔!)
from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: int
    stock: int = 0
どちらを使うべき?
データを保持するのが主な目的のクラスには@dataclassが最適です。複雑なロジックを持つクラスには通常のクラス定義が適しています。

まとめ

  • @dataclassで__init__、__repr__、__eq__を自動生成
  • ミュータブルなデフォルト値にはfield(default_factory=...)
  • __post_init__で初期化後のバリデーション・計算
  • frozen=Trueでイミュータブルなクラスを作成
  • order=Trueで比較・ソートが可能に