データクラス(dataclasses)は、クラス定義でありがちな初期化メソッドや表示メソッドの定型コードを自動生成してくれる仕組みです。
本記事では@dataclassで__init__
と__repr__
を自動生成する方法に焦点をあて、型ヒントを用いたフィールド定義、デフォルト値のルール、field()
での制御、そして実用的な注意点まで丁寧に解説します。
dataclassesの基本
@dataclassとは
dataclassesはPython 3.7以降で利用できる標準ライブラリで、クラスに@dataclass
デコレータを付けるだけで、初期化(__init__
)や表現(__repr__
)などのメソッドを自動生成します。
これにより、値オブジェクト(Value Object)やDTOのようなデータ中心のクラスを短く安全に書けます。
最小例として、名前と年齢を持つ人クラスを定義し、インスタンス化と表示を行います。
# person_basic.py
from dataclasses import dataclass
@dataclass # __init__ と __repr__ などが自動生成される
class Person:
name: str # 型ヒントがフィールド定義になる
age: int
# 自動生成された __init__ を使って生成
p = Person(name="Alice", age=30)
# 自動生成された __repr__ で見やすく表示される
print(p)
Person(name='Alice', age=30)
Python 3.7+での使い方
Python 3.7以上でfrom dataclasses import dataclass, field
として読み込みます。
基本の使い方はどのバージョンでも共通ですが、一部のオプション(例:cst-code>slotsやkw_only
)は3.10+で追加されています。
この記事では、特に__init__と__repr__の自動生成に関係する項目に絞って説明します。
主なデコレータ引数の概要は次の通りです。
引数 | 既定値 | 概要 |
---|---|---|
init | True | __init__ を自動生成するか |
repr | True | __repr__ を自動生成するか |
eq | True | __eq__ を自動生成するか |
order | False | 順序比較メソッドを自動生成するか |
frozen | False | 不変(イミュータブル)にするか |
slots(3.10+) | False | __slots__ を使ってメモリ効率化するか |
kw_only(3.10+) | False | すべてのフィールドをキーワード専用引数にするか |
自動生成されるメソッド
@dataclass
は既定値で以下を生成します。
__init__(...)
: フィールドに基づく初期化関数__repr__(self)
: クラス名とフィールドを見やすく文字列化__eq__(self, other)
: 値の等価性で比較(フィールドベース)
__str__は自動生成されませんが、print(obj)
は__str__
未実装時に__repr__
の結果を表示するため、実用上は見やすく表示されます。
__init__の自動生成
型ヒントでフィールド定義
dataclassのフィールドは型ヒントで定義します。
型ヒントがない属性はdataclass
の対象になりません。
# init_typing.py
from dataclasses import dataclass
@dataclass
class Book:
title: str # 必須フィールド
price: int # 必須フィールド
author: str = "N/A" # デフォルト値つき(省略可能)
b = Book(title="Effective Python", price=3800)
print(b)
Book(title='Effective Python', price=3800, author='N/A')
ここではtitle
とprice
が必須で、author
は省略可能な引数として__init__
に自動的に並びます。
デフォルト値と順序のルール
非デフォルト(必須)フィールドは、デフォルトつきのフィールドより前に定義しなければなりません。
これはPythonの関数定義のルールに由来します。
次の例はあえて順序を誤り、どのようなエラーになるかを示します。
# wrong_order.py
from dataclasses import dataclass
try:
@dataclass
class User:
name: str = "anonymous" # デフォルトあり(省略可能)
age: int # デフォルトなし(必須) ← 後ろに置くとNG
except TypeError as e:
print(type(e).__name__, ":", e)
TypeError : non-default argument 'age' follows default argument
正しくは必須を前、デフォルトありを後ろに並べます。
# correct_order.py
from dataclasses import dataclass
@dataclass
class User:
age: int
name: str = "anonymous" # 必須の後ろに置く
u = User(age=20)
print(u)
User(age=20, name='anonymous')
fieldで初期化を制御
field()
を使うと、__init__への参加やデフォルト値の生成方法を細かく制御できます。
init=False
: __init__の引数から除外default
: 固定のデフォルト値default_factory
: 呼び出されて値を生成する工場関数
# field_control.py
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class Session:
user_id: int
# 生成時に自動で現在時刻をセット。__init__では受け取らない
created_at: datetime = field(default_factory=datetime.now, init=False)
s = Session(user_id=123)
print(s) # created_at は自動で埋まっている
Session(user_id=123, created_at=datetime.datetime(2025, 1, 1, 12, 34, 56, 789012))
default_factoryは特にミュータブルな既定値を安全に用意したいときに重要です。
これについては後述します。
__repr__の自動生成
__repr__の出力例
自動生成される__repr__
は、クラス名と全フィールドを明示的に表示します。
デバッグで非常に便利です。
# repr_basic.py
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
pt = Point(10, 20)
print(repr(pt)) # 明示的に __repr__ を呼ぶ
print(pt) # print でも同等に表示される
Point(x=10, y=20)
Point(x=10, y=20)
表示を抑えるrepr=False
パスワードやトークンなど表示してはいけない値はfield(repr=False)
で__repr__
から除外できます。
# repr_hide.py
from dataclasses import dataclass, field
@dataclass
class Account:
username: str
password: str = field(repr=False) # 表示から除外
acc = Account("bob", "secret")
print(acc) # password は表示されない
Account(username='bob')
必要最小限の情報だけを表示することで、ログに機密情報が残るリスクを減らせます。
デバッグでの活用
__repr__が整っていると、printデバッグでも状態が一目瞭然です。
多段のオブジェクトを入れ子にしても、各階層の情報が読みやすく出力されます。
必要に応じてrepr=False
や__repr__
の手動実装を組み合わせると、ログの質をさらに高められます。
実用例と注意点
既存クラスを@dataclassに置き換え
手書きの__init__
と__repr__
を@dataclass
で置き換えると、コードが簡潔になり保守性が上がります。
# before_after.py
# 置き換え前(手書き)
class ProductManual:
def __init__(self, id: int, name: str, price: int = 0):
self.id = id
self.name = name
self.price = price
def __repr__(self) -> str:
return f"ProductManual(id={self.id!r}, name={self.name!r}, price={self.price!r})"
# 置き換え後(dataclass)
from dataclasses import dataclass
@dataclass
class Product:
id: int
name: str
price: int = 0
pm = ProductManual(1, "Keyboard", 4200)
pd = Product(1, "Keyboard", 4200)
print(pm)
print(pd)
print(pm.__repr__() == pd.__repr__()) # 出力形式の近さをざっくり比較
ProductManual(id=1, name='Keyboard', price=4200)
Product(id=1, name='Keyboard', price=4200)
False
最後の比較がFalse
なのは、クラス名が異なるためです。
dataclass版は標準の見やすい形式で出力され、手書きの冗長さがなくなります。
ミュータブルはdefault_factoryを使う
リストや辞書などのミュータブル(変更可能)な既定値を直接[]
や{}
で書いてはいけません。
全インスタンスで同じオブジェクトが共有されてしまいます。
必ずdefault_factory
を使います。
悪い例:
# mutable_bad.py
from dataclasses import dataclass
@dataclass
class TagBox:
tags: list[str] = [] # 悪い: 全インスタンスで共有される
a = TagBox()
b = TagBox()
a.tags.append("python")
print("a:", a.tags)
print("b:", b.tags) # b にも影響してしまう
a: ['python']
b: ['python']
良い例:
# mutable_good.py
from dataclasses import dataclass, field
@dataclass
class TagBox:
tags: list[str] = field(default_factory=list) # インスタンスごとに新しいリスト
a = TagBox()
b = TagBox()
a.tags.append("python")
print("a:", a.tags)
print("b:", b.tags) # 影響しない
a: ['python']
b: []
default_factoryはミュータブルの既定値を安全に用意する最重要テクニックです。
よくあるエラーと対処方法
初学者が遭遇しやすい問題と対処をまとめます。
- 非デフォルトが後ろにある
上で示したようにTypeError: non-default argument ... follows default argument
になります。必須フィールドを先、デフォルトつきを後に並べ替えます。 - 凍結(frozen)後の代入
@dataclass(frozen=True)
は不変オブジェクトを作ります。あとから代入すると例外になります。
# frozen_error.py
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
debug: bool
cfg = Config(debug=True)
try:
cfg.debug = False # 代入禁止
except Exception as e:
print(type(e).__name__, ":", e)
FrozenInstanceError : cannot assign to field 'debug'
対処としては、frozen=False
にするか、変更不要な設計にします。
- 必須引数の渡し忘れ
__init__
が必須と判断したフィールドを渡し忘れるとTypeError
になります。
# missing_arg.py
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
try:
u = User(name="Carol") # age を渡し忘れ
except TypeError as e:
print(type(e).__name__, ":", e)
TypeError : User.__init__() missing 1 required positional argument: 'age'
解決は単純で、必須フィールドをすべて指定するか、必要に応じてデフォルト値
やkw_only(3.10+)
で意図を明確にします。
補足: ミュータブルの既定値を直接書いても例外にならない場合がありますが、共有バグの温床です。
必ずdefault_factory
を使うことを習慣にしてください。
まとめ
@dataclassは、__init__
と__repr__
を自動生成して定型コードを削減し、読みやすく安全なデータ中心クラスを簡単に作成できます。
要点は次の通りです。
型ヒントでフィールドを定義し、非デフォルトを先、デフォルトを後に並べること、そしてミュータブルの既定値にはdefault_factory
を使うことです。
表示上の配慮が必要なフィールドはrepr=False
で隠し、field()
で初期化の挙動を細かく調整できます。
まずは小さな既存クラスから置き換えてみて、自動生成の恩恵とバグの減少を体感してみてください。