閉じる

@dataclassで__init__と__repr__を自動生成する方法を解説

データクラス(dataclasses)は、クラス定義でありがちな初期化メソッドや表示メソッドの定型コードを自動生成してくれる仕組みです。

本記事では@dataclassで__init____repr__を自動生成する方法に焦点をあて、型ヒントを用いたフィールド定義、デフォルト値のルール、field()での制御、そして実用的な注意点まで丁寧に解説します。

dataclassesの基本

@dataclassとは

dataclassesはPython 3.7以降で利用できる標準ライブラリで、クラスに@dataclassデコレータを付けるだけで、初期化(__init__)表現(__repr__)などのメソッドを自動生成します。

これにより、値オブジェクト(Value Object)やDTOのようなデータ中心のクラスを短く安全に書けます。

最小例として、名前と年齢を持つ人クラスを定義し、インスタンス化と表示を行います。

Python
# 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__の自動生成に関係する項目に絞って説明します。

主なデコレータ引数の概要は次の通りです。

引数既定値概要
initTrue__init__ を自動生成するか
reprTrue__repr__ を自動生成するか
eqTrue__eq__ を自動生成するか
orderFalse順序比較メソッドを自動生成するか
frozenFalse不変(イミュータブル)にするか
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の対象になりません。

Python
# 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')

ここではtitlepriceが必須で、authorは省略可能な引数として__init__に自動的に並びます。

デフォルト値と順序のルール

非デフォルト(必須)フィールドは、デフォルトつきのフィールドより前に定義しなければなりません。

これはPythonの関数定義のルールに由来します。

次の例はあえて順序を誤り、どのようなエラーになるかを示します。

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

正しくは必須を前、デフォルトありを後ろに並べます。

Python
# 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: 呼び出されて値を生成する工場関数
Python
# 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__は、クラス名と全フィールドを明示的に表示します。

デバッグで非常に便利です。

Python
# 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__から除外できます。

Python
# 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で置き換えると、コードが簡潔になり保守性が上がります。

Python
# 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を使います

悪い例:

Python
# 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']

良い例:

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はミュータブルの既定値を安全に用意する最重要テクニックです。

よくあるエラーと対処方法

初学者が遭遇しやすい問題と対処をまとめます。

  1. 非デフォルトが後ろにある
    上で示したようにTypeError: non-default argument ... follows default argumentになります。必須フィールドを先、デフォルトつきを後に並べ替えます。
  2. 凍結(frozen)後の代入
    @dataclass(frozen=True)は不変オブジェクトを作ります。あとから代入すると例外になります。
Python
# 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にするか、変更不要な設計にします。

  1. 必須引数の渡し忘れ
    __init__が必須と判断したフィールドを渡し忘れるとTypeErrorになります。
Python
# 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()で初期化の挙動を細かく調整できます。

まずは小さな既存クラスから置き換えてみて、自動生成の恩恵バグの減少を体感してみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!