CHAPTER 4 ・ コードを整理して育てる / Lesson 16

dataclass と型ヒント

データを入れる箱を、数行で定義する。型ヒントを添えて、何が入るのかをコードに書き残します。

目安 20分 ・ インストール不要

このレッスンが終わったら

「dataclass で、データ用のクラスを短く作れる。型ヒントは実行を変えないが、読む人とAIへの説明になる」と言える。

STEP 1

こんな場面、ありませんか?

売上明細の1行(伝票番号・日付・数量・単価)を、Lesson 15 のクラスで「1つの物」として持ってみます。すると、データを入れるだけのクラスでも、かなり長く書くことになります。

Lesson 15 の書き方:データ入れのクラスコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

同じ名前(slip_no など)を、__init__ の引数、self.○○ = ○○、__repr__ と、3回ずつ書いています。項目が増えると、書く量も、書き間違いも増えます。

この「データを入れる箱」を、数行で作れるのが、この回の主役、dataclass(データクラス)です。もう1つの主役、型ヒント(何が入る予定かの書き添え)も、あわせて学びます。

STEP 2

dataclass で書いてみる

同じ4つの項目を、dataclass で書きます。

dataclass で、同じ箱を作るコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

__init__ も __repr__ も書いていないのに、作れて、print で中身が読める形で表示されました。しかも、項目名は1回ずつ書くだけです。

STEP 3

仕組みを読む

slip_no: str の部品
slip_no項目の名前(属性の名前)
:「これは〜の予定」の印
str入れる値の型(文字)
dataclass を、1行ずつ日本語にしてみる
@dataclassこの下のクラスを、「データ入れ」として作る。__init__ と __repr__ を自動で作ってくれる
class SalesLine:SalesLine(売上明細の1行)という設計図を作る
slip_no: str「伝票番号」という欄を作る。文字(str)が入る予定
qty: int「数量」という欄を作る。整数(int)が入る予定

日本語にすると

売上明細の1行は、伝票番号(文字)・日付(文字)・数量(整数)・単価(整数)を持つ、データ入れとして作ってください。作り方と表示の仕方は、自動で用意してください。

@dataclass のように、クラスや関数の上に @ で付ける書き方をデコレータ(飾り)と呼びます。「このクラスに、便利な機能を足す」という印だと思えば十分です。

: str や : int が、型ヒントです。よく使う型は、次の通りです。

書き方意味
int / str / float / bool整数 / 文字 / 小数 / 真偽
list[str]文字のリスト
dict[str, int]キーが文字、値が整数の辞書
str | None文字、または None(値がない)
STEP 4

変えて、試す

① 型ヒントは、守られない。 型ヒントは「こういう値が入る予定」という書き添えで、Pythonは守らせません。CSVから読んだ数字は文字なので、ためしに文字のまま入れてみます。

わざと:数字のところに、文字を入れるコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

作る時点ではエラーにならず、print もできました(qty='5' と、引用符付きで文字だと分かります)。ところが、掛け算で TypeError になりました。型ヒントは、エラーを防いでくれるのではなく、読む人(とAI)への説明です。実際に変換するのは、次の __post_init__ の仕事です。

② 作った直後に、変換と点検をする。 __post_init__ は、dataclass が値を受け取った直後に自動で動くメソッドです。ここで int() に変換し、おかしな値は ValueError で止めます。sales_detail.csv を、まるごと SalesLine にしてみます。

CSV の全行を SalesLine にして、合計を出すサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

128件が読めて、9月の合計は Lesson 13 で求めた 1036500 と一致しました。month_total(lines: list[SalesLine], month: str) -> int のように、関数の引数と戻り値にも型ヒントを付けられます(-> のあとが、戻り値の型です)。実行には影響しませんが、「何を渡して、何が返るか」が、関数の1行目で読めます。

③ 書き換えを禁止する:frozen=True。 一度作ったデータを、うっかり書き換えたくないときは、@dataclass(frozen=True) にします。

わざと:frozen なデータを書き換えるコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

FrozenInstanceError(固定されたデータは書き換えられません)と出ます。請求や入金のデータなど、後から変わってはいけないものに向いています。

④ 初期値と、リストの初期値。 初期値は = 値 で付けます。リストなどを初期値にするときは、Lesson 13 の罠と同じ理由で、field(default_factory=list) と書きます。

初期値つきの項目コードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

tags: list[str] = [] と書くと、ValueError: mutable default ... use default_factory と、Python が止めてくれます。a と b の tags は別々のリストになっています。

STEP 5

自分で書く

お題① 入金1件を表す Payment を、dataclass で定義してください。項目は、date(文字)・memo(文字)・amount(整数)です。3件のリストから、入金額の合計を表示します。

お題①:dataclass を定義して合計を出すコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

@dataclass の下に class Payment: を書き、date: str / memo: str / amount: int の3行を並べます。

答えの例を見る
from dataclasses import dataclass

@dataclass
class Payment:
  date: str
  memo: str
  amount: int

payments = [
  Payment("2025-10-28", "INV-2025-0001", 1320000),
  Payment("2025-10-29", "INV-2025-0002", 484000),
  Payment("2025-10-30", "INV-2025-0004", 230000),
]
print(sum(p.amount for p in payments))

お題② expenses.csv の各行を Expense にします。CSVの金額は文字なので、__post_init__ で int に直してください。そのうえで、総務部の金額の合計を表示します。

お題②:__post_init__ で整数に直すサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

def __post_init__(self): の中で、self.amount = int(self.amount) と書きます。字下げ(インデント)をそろえましょう。

答えの例を見る
import csv
from dataclasses import dataclass

@dataclass
class Expense:
  no: str
  dept: str
  amount: int

  def __post_init__(self):
      self.amount = int(self.amount)

with open("expenses.csv", encoding="utf-8") as f:
  expenses = [Expense(r["申請ID"], r["部署"], r["金額"]) for r in csv.DictReader(f)]

print(sum(e.amount for e in expenses if e.dept == "総務"))
STEP 6

仕事ならどう使う?

理解チェック@dataclass class P: qty: int のクラスに、P("5") と文字を渡しました。どうなるでしょう?

理解チェックdataclass を使うと、自動で作られるものは、どれでしょう?