一覧PG-13-3 · オブジェクトとクラス

メソッドの継承とsuper

通知文を作るクラスで、親の実装、上書きした実装、superで組み合わせた実装を実行します。最後は複数のクラスを継承し、定義された探索順が呼出し順へどう反映されるか確認します。

説明

継承では、別のクラスで定義した属性やメソッドを引き継ぎ、必要な部分を追加・変更できます。同じ名前のメソッドを子クラスで定義すると、呼ばれる実装が変わります。

通知文を作るクラスで、親の実装、上書きした実装、superで組み合わせた実装を実行します。最後は複数のクラスを継承し、定義された探索順が呼出し順へどう反映されるか確認します。

1基本の通知文

受け取った文字列をそのまま返す親クラスを定義します。

class Message:基本のクラス def render(self, text):通知文を返すメソッド return text

renderが通知文を作る共通の呼出し口です。このクラスでは装飾を加えません。

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

1
class Message: def render(self, text): return text
2
class PlainMessage(Message): pass
3
base = Message(); plain = PlainMessage(); print(base.render("ready"), plain.render("ready")); print(isinstance(plain, Message), type(plain) is Message)
ready ready
True False
4
class UpperMessage(Message): def render(self, text): return text.upper()
5
for message in (Message(), UpperMessage()): print(type(message).__name__, message.render("ready"))
Message ready
UpperMessage READY
6
class FramedMessage(UpperMessage): def render(self, text): return "[" + super().render(text) + "]"
7
framed = FramedMessage(); print(framed.render("ready")); print([c.__name__ for c in FramedMessage.__mro__])
[READY]
['FramedMessage', 'UpperMessage', 'Message', 'object']
8
class Prefix(Message): def render(self, text): return ">" + super().render(text)
9
class Suffix(Message): def render(self, text): return super().render(text) + "!"
10
class Both(Prefix, Suffix): pass
11
print([c.__name__ for c in Both.__mro__]); print(Both().render("ready"))
['Both', 'Prefix', 'Suffix', 'Message', 'object']
>ready!

演習完了