到達点は、関連するデータとルールをまとめ、共有してよいものを区別することです。前提は第03・06回です。
クラスは状態と操作の単位
selfは操作対象のインスタンスです。各インスタンスの状態はselfに保存し、クラス属性として可変リストを置くと全インスタンスで共有される点に注意します。データを運ぶ型にはdataclassを使うと、初期化や比較の定型処理を減らせます。
from dataclasses import dataclass, field, replace
@dataclass(frozen=True)
class Lesson:
title: str
minutes: int
def __post_init__(self):
if not self.title.strip() or self.minutes < 0:
raise ValueError("題名と時間が不正です")
@dataclass
class Plan:
lessons: list[Lesson] = field(default_factory=list)
def total(self) -> int:
return sum(lesson.minutes for lesson in self.lessons)
first = Plan()
second = Plan()
lesson = Lesson("クラス", 25)
first.lessons.append(lesson)
print(first.total(), second.total())
print(replace(lesson, minutes=30))
main.pyで実行すると25と0、新しい30分のLessonが出ます。default_factoryはインスタンスごとに新しいリストを作ります。frozen=Trueはフィールドの通常の再代入を禁止しますが、内部に可変オブジェクトがあれば、その中身まで自動で凍結しません。
継承より先に「持つ」関係を考える
通知方法を変えたいなら、サービスが送信オブジェクトや関数を受け取る形にできます。巨大な継承階層を作るより、差し替えたい機能を部品として渡す方が簡単な場合があります。Pythonでは同じ操作を提供するオブジェクトを使うダックタイピングがよく使われます。型として明示する方法は第10回で扱います。
Pythonの先頭アンダースコアは「内部用」の慣習です。Javaのprivateと同じ強制的なアクセス制御ではありません。重要なルールは、利用者へ公開する操作とデータの作り方を通して守ります。
練習と解答
練習:Lessonのminutesへ文字列を渡したら、型注釈だけで防げるでしょうか。
解答:型注釈は通常の実行時に自動検査しません。例の大小比較でTypeErrorになる可能性があります。外部入力なら先に型を検査し、内部コードの間違いなら静的型チェックとテストで発見します。dataclass自体が入力検証ライブラリになるわけではありません。
公式資料
クラスとdataclassesが参照先です。
Python全20回の目次 | 前の回 | 次の回