1. __str__ と __repr__
特殊メソッド(ダンダーメソッド)とは __名前__ の形をした、Python が自動で呼び出す特別なメソッドです。
print()、len()、+ などの組み込み操作は、内部でこれらの特殊メソッドを呼び出すことで動いています。
🔑 特殊メソッドを定義する理由: 自作クラスのオブジェクトに対して print()・len()・+ などの標準的な書き方を使えるようにするためです。
定義しなければどうなるか? — __str__ のない Patient クラスで print(p) すると次のように表示されます:
<__main__.Patient object at 0x7f4a2c3b0d90> ← メモリアドレス(人には読めない)
__str__ を定義することで、print(p) が自動的にそのメソッドを呼び出し「患者: 田中太郎(45歳)」のような読みやすい表示に変わります。
__str__ は print(obj) や str(obj) で呼ばれるユーザー向けの表示、
__repr__ は repr(obj) で呼ばれる開発者向けの詳細表示です。
リストに入れて print() したときは __repr__ が使われます。
💡 内部の呼び出しの仕組み:
print(p) → p.__str__() を自動で呼び出す str(p) → p.__str__() を呼び出す repr(p) → p.__repr__() を呼び出す print([p1,p2]) → 各要素の __repr__() を呼び出す
自分で p.__str__() と書かなくても、print(p) と書くだけで Python が裏側で代わりに呼んでくれます。__str__ がない場合は __repr__ が代用され、それもない場合はメモリアドレス表示になります。
2. __len__ と __getitem__
__len__ を定義すると len(obj) が使えます。
__getitem__ を定義すると obj[i] のインデックスアクセスができます。
ここで重要なことに気づきます。今まで当たり前のように使ってきた len(my_list) や
my_list[0] も、実は内部で特殊メソッドを呼び出していたのです。
🎉 len() と [] の謎が解けた!
len([1, 2, 3]) → [1, 2, 3].__len__() を呼んでいた! my_list[0] → my_list.__getitem__(0) を呼んでいた! "abc"[1] → "abc".__getitem__(1) を呼んでいた!
組み込みの list・str・tuple などには、Python 開発者があらかじめ __len__ や __getitem__ を実装しています。だから最初から len() や [] が使えたのです。
💡 「len() や [] はもともと使えるのに、なぜ自作クラスでも定義が必要?」
組み込みの list や str には Python 開発者がすでに __len__ などを実装しています。ところが自作の PatientList クラスは Python が初めて見るクラスです。「何個入っているのか」「どうインデックスにアクセスするのか」を Python は知る方法がありません。
__len__ を定義することで「このクラスの長さは self._data の長さですよ」と Python に教えます。これが Python の プロトコル(duck typing) という設計思想で、「__len__ を持っていれば長さを持つもの、__getitem__ を持っていればインデックスアクセスできるもの」とみなします。
3. 比較演算子のオーバーロード
__eq__、__lt__、__le__ などを定義すると、
==、<、<= などの演算子が使えます。
sorted() などの組み込み関数とも連動します。
__eq__ を定義しない場合は、== が「同じオブジェクトかどうか(メモリアドレスが同じか)」を比較します。
つまり中身が全く同じでも別々に作ったオブジェクト同士は False になります。
__eq__ を定義することで「どの属性が一致すれば等しいとみなすか」を自分で決められます。
📋 定義前 vs 定義後の違い:
p1 = Patient("田中太郎", 45)
p2 = Patient("田中太郎", 45)
# __eq__ なし → False(別々に作ったオブジェクトだから)
# __eq__ あり → True(name と age が一致するから)
📋 比較演算子と対応する特殊メソッド:
__eq__(self, other) → a == b __ne__(self, other) → a != b __lt__(self, other) → a < b (sorted() はこれだけで動く) __le__(self, other) → a <= b __gt__(self, other) → a > b __ge__(self, other) → a >= b
__lt__ を定義するだけで sorted() が使えます。6つ全部定義したい場合は from functools import total_ordering デコレータを使うと、__eq__ と1つの不等号メソッドだけ書けば残りが自動補完されます。
4. 算術演算子のオーバーロード
__add__・__mul__・__sub__ などで
+・*・- 演算子を定義できます。
PyTorch の Tensor が a + b や a * 2 で演算できるのもこの仕組みです。
a + b を書くと Python は a.__add__(b) を呼び出します。
__rmul__ については、コードを動かした後の補足で解説します。
💡 なぜ __rmul__ が必要なのか:
vector * 2 と 2 * vector はどちらも同じ結果ですが、Pythonが内部で試みる順番が違います。
| 書き方 | Pythonが試みること | 結果 |
|---|---|---|
vector * 2 | vector.__mul__(2) を呼ぶ | ✅ __mul__ を定義済みなので動く |
2 * vector | ① まず int.__mul__(2, vector) を試みる② int は Vector を知らないので失敗 ③ 次に vector.__rmul__(2) を試みる | ✅ __rmul__ があれば動く❌ なければ TypeError |
__rmul__(right multiply)は「* 演算子の右側のオペランドになったときのかけ算」用の予備メソッドです(2 * vector では vector が * の右側にあります)。今回は return self.__mul__(scalar) と書くだけで __mul__ に処理を任せられます。
📋 算術演算子と対応する特殊メソッド:
__add__(self, other) → a + b __sub__(self, other) → a - b __mul__(self, other) → a * b __truediv__(self, other) → a / b __floordiv__(self, other) → a // b __mod__(self, other) → a % b __rmul__(self, other) → b * a (左辺が未対応のときのフォールバック)
💡 PyTorch文脈: tensor_a + tensor_b・tensor * 2.0・tensor @ weight(行列積)はすべてこの仕組みで動いています。NumPy や PyTorch の配列演算がなぜあれほど直感的に書けるのか、その答えがここにあります。
5. 特殊メソッドは全部定義する必要があるの?
ここまで多くの特殊メソッドを見てきましたが、「クラスを作るたびに全部定義しなければいけないの?」と思うかもしれません。 答えは「必要なものだけ定義すればOK」です。
len()・[]・+・比較演算子など多くの操作は、対応する特殊メソッドが定義されていないと TypeError になります
(例:__len__ がないクラスで len() を使うと TypeError: object of type '...' has no len())。
ただし __eq__(未定義時はオブジェクトの同一性で比較)や __str__/__repr__(未定義時はデフォルト表示にフォールバック)など、
Python がデフォルト動作を提供する特殊メソッドもあります。
使いたい操作に対応するメソッドだけを定義すれば十分で、使わない操作のメソッドは定義しなくてかまいません。
| 特殊メソッド | いつ定義する? | 定義しないと? |
|---|---|---|
__init__ | ほぼ必ず(初期化したい値があれば) | 引数なしで作られる |
__str__ / __repr__ | デバッグ・表示を読みやすくしたいとき | <ClassName object at 0x...> と表示される |
__len__ / __getitem__ | コンテナ(リストのような)クラスを作るとき | len() / [] が使えない |
__eq__ / __lt__ | == や sorted() で比較させたいとき | メモリアドレスで比較される |
__add__ 等 | 数値的・ベクトル的な演算をさせたいとき | + などが使えない |
💡 実際のコードでの頻度:
普通のデータクラス(患者情報・設定データなど)なら __init__ と __str__ の2つだけで十分なことがほとんどです。
__len__ や演算子オーバーロードは「リストのように使えるクラス」「NumPy配列のように計算できるクラス」など、
特定の用途を持つクラスに限定して使います。
「使いたい操作が出てきたときに追加する」くらいのスタンスで大丈夫です。
6. 練習問題
__str__ の実装
Patient クラスに __str__ を追加し、print(p) で「田中太郎さん(45歳)/ 診断: 高血圧」と表示されるようにしてください。
ヒントを見る(答え+解説)
class Patient:
def __init__(self, name, age, diagnosis):
self.name = name
self.age = age
self.diagnosis = diagnosis
def __str__(self):
return f"{self.name}さん({self.age}歳)/ 診断: {self.diagnosis}"
p = Patient("田中太郎", 45, "高血圧")
print(p)
# → 田中太郎さん(45歳)/ 診断: 高血圧
__str__ は print() や str() で呼ばれる特殊メソッドです。f-string で整形した文字列を return するだけで、オブジェクトを人が読みやすい形で表示できます。
患者リストに __len__ と __getitem__ を追加
下記の Ward クラスに __len__ と __getitem__ を実装して、len(ward) と ward[0] が動くようにしてください。
ヒントを見る(答え+解説)
class Ward:
def __init__(self, name):
self.name = name
self.patients = []
def admit(self, patient_name):
self.patients.append(patient_name)
def __len__(self):
return len(self.patients)
def __getitem__(self, index):
return self.patients[index]
w = Ward("4階東病棟")
w.admit("田中太郎")
w.admit("山田花子")
w.admit("佐藤次郎")
print(f"入院患者数: {len(w)}名")
print(f"最初の患者: {w[0]}")
print(f"最後の患者: {w[-1]}")
# → 入院患者数: 3名 / 最初の患者: 田中太郎 / 最後の患者: 佐藤次郎
__len__ を実装すると len() が使え、__getitem__ を実装するとインデックスアクセス obj[i] が使えます。自作クラスをリストのように扱えるようになります。
年齢で比較できるPatientクラス
Patient クラスに __lt__ を実装して sorted() で年齢昇順に並べ替えできるようにしてください。
ヒントを見る(答え+解説)
class Patient:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"{self.name}({self.age})"
def __lt__(self, other):
return self.age < other.age
patients = [
Patient("田中太郎", 45),
Patient("山田花子", 62),
Patient("佐藤次郎", 33),
Patient("鈴木一郎", 28),
]
print("年齢昇順:", sorted(patients))
print("年齢降順:", sorted(patients, reverse=True))
# → 年齢昇順: [鈴木一郎(28), 佐藤次郎(33), 田中太郎(45), 山田花子(62)]
# → 年齢降順: [山田花子(62), 田中太郎(45), 佐藤次郎(33), 鈴木一郎(28)]
__lt__(less than)を実装すると sorted() や < 演算子で比較できるようになります。return self.age < other.age の1行だけで年齢順のソートが可能になります。
まとめ
このレッスンのポイント
__str__→print(obj)の表示をカスタマイズ(ユーザー向け)__repr__→repr(obj)やリスト表示(開発者向け)__len__→len(obj)、__getitem__→obj[i]を有効化__eq__→==、__lt__→<(sorted()と連動)__add__,__mul__→ 算術演算子を定義(PyTorchのTensorと同じ仕組み)
自由に試してみましょう:
完了するとトップページに進捗が表示されます
