メソッドの継承とsuper
通知文を作るクラスで、親の実装、上書きした実装、superで組み合わせた実装を実行します。最後は複数のクラスを継承し、定義された探索順が呼出し順へどう反映されるか確認します。
説明
継承では、別のクラスで定義した属性やメソッドを引き継ぎ、必要な部分を追加・変更できます。同じ名前のメソッドを子クラスで定義すると、呼ばれる実装が変わります。
通知文を作るクラスで、親の実装、上書きした実装、superで組み合わせた実装を実行します。最後は複数のクラスを継承し、定義された探索順が呼出し順へどう反映されるか確認します。
1基本の通知文
受け取った文字列をそのまま返す親クラスを定義します。
class Message:基本のクラス
def render(self, text):通知文を返すメソッド
return textrenderが通知文を作る共通の呼出し口です。このクラスでは装飾を加えません。
2何も上書きしない子クラス
Messageを継承し、追加の定義を持たないクラスを作ります。
class PlainMessage(Message):Messageを継承
pass追加の定義なしPlainMessage自身にrenderがないので、呼出し時には親のMessageから見つかります。passはこのクラス本体では何もしないという文です。
3継承した実装を呼ぶ
親と子から生成し、同じメソッドを呼びます。
base = Message(); plain = PlainMessage()親と子を生成; print(base.render("ready"), plain.render("ready")同じ呼出しを比較); print(isinstance(plain, Message)親を含む型の判定, type(plain) is Message型の完全一致)両方ともreadyを返します。plainはMessageのインスタンスとしても扱えるためisinstanceはTrueですが、実際の型はPlainMessageなのでtypeの完全一致はFalseです。
4同じメソッド名を上書き
子クラス側でrenderを定義します。
class UpperMessage(Message):Messageを継承
def render(self, text):同名メソッドを上書き
return text.upper()大文字へ変換UpperMessageではrenderが見つかるので、その実装が呼ばれます。このコードは親のrenderを自動では呼びません。
5対象によって変わる実装
同じ呼出しを二つの型へ行います。
for message in (Message(), UpperMessage()):異なる型の二つの対象
print(type(message).__name__, message.render("ready")同じ呼出し口)Messageはready、UpperMessageはREADYを返します。利用側の呼出し方は同じでも、対象の型によって選ばれる実装が変わります。
6superで既存処理を利用
UpperMessageを継承し、大文字化された結果を角括弧で囲みます。
class FramedMessage(UpperMessage):大文字化の実装を継承
def render(self, text):
return "[" + super().render(text)探索順の次の実装を呼ぶ + "]"super().renderは、この対象のメソッド探索順でFramedMessageより後ろにある実装を探します。この継承関係ではUpperMessage.renderを呼び、その戻り値を囲みます。
7戻り値と探索順
実行結果と、__mro__に記録されたメソッド探索順を表示します。
framed = FramedMessage()装飾するインスタンス; print(framed.render("ready")大文字化と括弧を組み合わせる); print([c.__name__ for c in FramedMessage.__mro__メソッド探索順])結果は[READY]です。探索順はFramedMessage、UpperMessage、Message、objectです。superは常に決め打ちの親一つを指す名前ではなく、この順序に基づいて次の実装を探します。
8先頭に記号を加えるクラス
複数継承でも処理をつなげるため、superで次へ渡すクラスを作ります。まず先頭に記号を加えます。
class Prefix(Message):先頭に記号を加える
def render(self, text):
return ">" + super().render(text)次の実装へ渡すsuperの戻り値に>を加えて返します。どの実装が次になるかは、最終的に生成する対象の探索順によって決まります。
9末尾に記号を加えるクラス
もう一つ、末尾に記号を加えるクラスを作ります。
class Suffix(Message):末尾に記号を加える
def render(self, text):
return super().render(text)次の実装へ渡す + "!"こちらもsuperで次の実装を呼び、戻り値へ!を加えます。
10二つのクラスを継承
PrefixとSuffixをこの順で継承します。
class Both(Prefix, Suffix):二つをこの順序で継承
pass追加の上書きなし両方がMessageを継承する形ですが、探索順にはMessageが重複して現れません。次の操作で具体的な順序を確認します。
11複数継承での次の実装
Bothの探索順と戻り値を表示します。
print([c.__name__ for c in Both.__mro__複数継承の探索順]); print(Both().render("ready")連鎖した処理の結果)探索順はBoth→Prefix→Suffix→Message→object、結果は>ready!です。Prefix内のsuperは、この対象ではSuffixへ進みます。Prefixの宣言に書かれたMessageへ直接飛ぶわけではありません。
まとめ
子クラスに同名のメソッドがあれば、探索順に従ってその実装が選ばれます。
上書きしただけでは親の実装は呼ばれません。既存処理を利用する場合は明示的に呼び出します。
superは対象のメソッド探索順に沿って次の実装を探します。複数継承では、宣言上の親と次の実装が一致するとは限りません。
操作履歴と解説を振り返れます。
Python 3
class Message:
def render(self, text):
return textclass PlainMessage(Message):
passbase = Message(); plain = PlainMessage(); print(base.render("ready"), plain.render("ready")); print(isinstance(plain, Message), type(plain) is Message)ready ready True False
class UpperMessage(Message):
def render(self, text):
return text.upper()for message in (Message(), UpperMessage()):
print(type(message).__name__, message.render("ready"))Message ready UpperMessage READY
class FramedMessage(UpperMessage):
def render(self, text):
return "[" + super().render(text) + "]"framed = FramedMessage(); print(framed.render("ready")); print([c.__name__ for c in FramedMessage.__mro__])[READY] ['FramedMessage', 'UpperMessage', 'Message', 'object']
class Prefix(Message):
def render(self, text):
return ">" + super().render(text)class Suffix(Message):
def render(self, text):
return super().render(text) + "!"class Both(Prefix, Suffix):
passprint([c.__name__ for c in Both.__mro__]); print(Both().render("ready"))['Both', 'Prefix', 'Suffix', 'Message', 'object'] >ready!
演習完了