Q1次のコード result = a + b を実行したとき、Python が 裏で呼び出すメソッド はどれですか?
特殊メソッド — + や print の挙動をクラスに教える
Python の特殊メソッド(ダンダーメソッド)を解説します。__add__ で + をカスタマイズし、__str__ で print の表示を整え、__eq__ で == の比較を定義する流れまでを図解で押さえます。
前回まででインスタンスメソッド・クラスメソッド・スタティックメソッドの 3 種類のメソッドを整理しました。ここからは特殊なメソッド — 特殊メソッド(ダンダーメソッド) — を解説します。
特殊メソッドとは — 「ダンダーメソッド」
Python のクラスでよく見かける __init__ や __str__ のように、前後をアンダースコア 2 つで囲んだ名前のメソッドを 特殊メソッド と呼びます。アンダースコア(dunder = double underscore)に挟まれているため、ダンダーメソッド とも呼ばれます。
特殊メソッドの大きな特徴は、自分で直接呼び出すものではなく、Python が特定の操作をしたときに自動で呼んでくれる点です。たとえば v1 + v2 と書いたとき、Python は内部で v1.__add__(v2) を呼びに行きます。print(p) を書いたら p.__str__() が呼ばれ、a == b を書いたら a.__eq__(b) が呼ばれます。
+ や == が使えるようになる。__add__ で + 演算子を定義する
代表例として + を見てみます。たとえば「金額(円)を表す Money クラス」を作ったとして、Money(300) + Money(500) と書いて Money(800) を作りたい — ということがあります。何もせずに足し算しようとすると、Python は「Money 同士の足し方を知らない」と言って TypeError を出してしまいます。
この「足し方」をクラスに教えるのが __add__ メソッドです。def __add__(self, other): を定義して、自分(self)と相手(other)から新しいインスタンスを作って返すように書けば、+ 演算子で使えるようになります。
class Money:
def __init__(self, amount):
self.amount = amount
def __add__(self, other): # + のときに呼ばれる
return Money(self.amount + other.amount)
wallet = Money(300)
payment = Money(500)
total = wallet + payment # 内部的には wallet.__add__(payment)
print(total.amount) # 800
+ を書くと、Python が自動で self.__add__(other) を呼ぶ。self には左辺、other には右辺のインスタンスが入る。返した新しいインスタンスがそのまま + の結果になる。__str__ と __repr__ — 文字列表現を 2 種類用意する
次は 「インスタンスを文字列にする」 ときに呼ばれる特殊メソッドです。これには 2 つあって、目的が違います。
- __str__ — print(p) や str(p) のときに呼ばれる、ユーザー向けの見やすい文字列
- __repr__ — 対話シェルや開発者がデバッグ時に見たい、コードに近い形の詳細な文字列
何もしないと print(user) は <__main__.User object at 0x...> のような分かりづらい結果になります。__str__ を定義すると見やすい表示になり、さらに __repr__ も定義しておくとデバッグの際に型と中身が一目で分かるようになります。
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self): # print() / str() のとき
return f"{self.name}({self.age}歳)"
def __repr__(self): # 開発者向けデバッグ用
return f"User(name={self.name!r}, age={self.age})"
u = User("花子", 30)
print(u) # 花子(30歳) ← __str__
print(repr(u)) # User(name='花子', age=30) ← __repr__
print(u)やstr(u)のときに呼ばれる- 目的: エンドユーザーに見せる文字列
- 例:
花子(30歳)
repr(u)や対話シェルでの表示に呼ばれる- __str__ が無いときの print でも代用される
- 目的: 型と中身がはっきり分かるデバッグ表示
- 例:
User(name='花子', age=30)
__repr__ だけは書いておくとデバッグが楽になる。__eq__ で == の比較を定義する
次は等価性を見てみます。a == b と書いたとき、Python は内部的に a.__eq__(b) を呼びますが、自作クラスで __eq__ を定義していないと、デフォルトでは 「同じメモリ上のオブジェクトか」 (id の比較)になってしまいます。
たとえばクーポンを表す Coupon クラスがあって、コードが同じならクーポンとしては同じものとして扱いたい — そういう「業務上の等価性」を定義するのが __eq__ の仕事です。
__eq__ を 定義しないと id 比較になり、中身が同じでも別インスタンスなら False。__eq__ を 定義すると 中身(code)で比較できるようになる。class Coupon:
def __init__(self, code, discount):
self.code = code
self.discount = discount
def __eq__(self, other):
return self.code == other.code # コードが同じなら同じクーポン
c1 = Coupon("SPRING10", 0.10)
c2 = Coupon("SPRING10", 0.20) # 割引率は違うがコードは同じ
c3 = Coupon("SUMMER15", 0.15)
print(c1 == c2) # True (code が一致)
print(c1 == c3) # False (code が違う)
__eq__ を定義しないとどうなる?
__eq__ を定義しないクラスでは、c1 == c2 は 同一オブジェクトかどうか(メモリ上の同じ箱を指しているか)で判定されます。たとえコード・割引率がどちらも完全に同じでも、別々に作ったインスタンスは別のメモリ上に置かれるので結果は False になります。「中身で比較したい」場合は __eq__ を必ず自分で書きましょう。
他にもよく使う特殊メソッド — __call__ / __len__ / __bool__
ここまで紹介した 4 つ以外にも、よく使う特殊メソッドがいくつかあります。中でも代表的な 3 つを押さえておきましょう。
- __call__ — インスタンスを 関数のように () 付きで呼び出せる ようにする
- __len__ — len(obj) を呼んだときの返り値を決める
- __bool__ — if obj: や bool(obj) で 真偽値として評価される値を決める
どれも組み込みの操作 — 関数呼び出し、len()、if の条件評価 — を自作クラスに教えるためのメソッドです。アクセスログを溜めていく Logger クラスを例にすると、これら 3 つを 1 つのクラスに同居させて使い分けるイメージがつかみやすくなります。
class Logger:
def __init__(self, name):
self.name = name
self.log = []
def __call__(self, message): # logger("...") で呼べる
self.log.append(message)
return f"[{self.name}] {message}"
def __len__(self): # len(logger) のとき
return len(self.log)
def __bool__(self): # if logger: のとき
return len(self.log) > 0
app = Logger("app")
print(app("起動しました")) # [app] 起動しました
print(app("ログイン成功")) # [app] ログイン成功
print(len(app)) # 2
if app:
print("ログがあります") # ログがあります
logger(...) / len(logger) / if logger: のような 書き慣れた構文 を、自作クラスに 3 種類のダンダーで教え込んでいる。__bool__ を書かない場合の挙動
__bool__ を定義していない場合、Python は次に __len__ を見にいきます。__len__ の戻り値が 0 なら False、それ以外なら True と扱われます。両方とも未定義のクラスは、インスタンスがどんな状態でも常に True 扱いになります。空のリストや空文字列が False と評価されるのもこの仕組みです。
他にも __hash__(set や dict のキーにする)、__lt__ / __gt__(< / > の比較)、__getitem__(obj[key] の添字アクセス)、__iter__(for x in obj: での反復)など、特殊メソッドはたくさんあります。すべてを丸暗記する必要はなく、「組み込みの操作を自作クラスに教えたいときに、対応するダンダーがある」 ということを理解すれば、そのつど調べて使えるようになります。
| 特殊メソッド | 書く構文 | 呼ばれるタイミング |
|---|---|---|
| __init__ | Money(300) | インスタンス生成時 |
| __add__ | a + b | + 演算子 |
| __str__ | print(p) / str(p) | ユーザー向け文字列化 |
| __repr__ | repr(p) / 対話シェル表示 | 開発者向け文字列化 |
| __eq__ | a == b | 等価比較 |
| __call__ | obj(...) | 関数のような呼び出し |
| __len__ | len(obj) | 長さ取得 |
| __bool__ | if obj: / bool(obj) | 真偽値評価 |
理解度チェック
まずは1問ずつ答えてみましょう。
Q2__str__ と __repr__ の役割の説明として、最も適切 なものはどれですか?
Q3__eq__ を 定義していない クラスで a == b を実行すると、何が比較されますか?(a と b は同じクラスのインスタンス)