Q1次のコードresult = a + bを実行したとき、Python が裏で呼び出すメソッドはどれですか?
特殊メソッド — + や print の挙動をクラスに教える
Pythonの特殊メソッド(ダンダー)をMoney/User/Couponで解説。__add__で +、__str__と__repr__の使い分け、__eq__で == の意味を定義、__call__/__len__/__bool__の用途を確認できます。
前回まででインスタンスメソッド・クラスメソッド・スタティックメソッドの 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は同じクラスのインスタンス)