継承とポリモーフィズム
継承とは、既にあるクラスの属性とメソッドを受け継いで、新しいクラスを作る仕組みです。ポリモーフィズムとは、同じ呼び出しが、オブジェクトの種類によって違う動きをすることです。
2つを使うと、種類が増えても、呼ぶ側のコードを書き直さずに済みます。
このレッスンのゴール
メモ
継承でコードを使い回し、ポリモーフィズムで同じ呼び出しを種類ごとに変えることができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 継承 | 親のクラスの中身を、子のクラスが受け継ぐこと | class ChildTicket(Ticket) |
| 親クラスと子クラス | 受け継がれる側が親、受け継ぐ側が子。サブクラスとも呼ぶ | |
| オーバーライド | 親のメソッドを、子が同じ名前で書き直すこと | |
| super() | 子から、親のメソッドを呼ぶ書き方 | |
| ポリモーフィズム | 同じ呼び出しが、種類によって違う動きをすること | |
| is-a 関係 | 「子は親の一種だ」と言える関係 |
学生が入力するセルは、このプレビューでは空白です。
子クラスは親の中身を受け継ぐ
切符のクラス Ticket に、運賃を返す fare を書いたとします。子ども用の切符も、同じ作りです。
class ChildTicket(Ticket): と書くと、ChildTicket は Ticket の属性とメソッドをすべて受け継ぎます。違う所だけを書けば足ります。
子クラスで fare だけを置き換える
class Ticket:
def __init__(self, base_yen):
self.base_yen = base_yen
def fare(self):
return self.base_yen
# Ticket のすべてを受け継ぎ、fare だけを置き換えます。
class ChildTicket(Ticket):
def fare(self):
return self.base_yen // 2
print(Ticket(600).fare())
print(ChildTicket(600).fare())
# ChildTicket(800) に変えて、もう一度実行しましょう。
super() で親の計算を使う
子が親のメソッドを置き換えるとき、親の計算を残したいことがあります。そのときは super() で、親のメソッドを呼びます。
下の例では、親の運賃に200円を足しています。親の計算を書き写さないので、親を直せば子も直ります。
親の運賃に200円を足す
class Ticket:
def __init__(self, base_yen):
self.base_yen = base_yen
def fare(self):
return self.base_yen
class NightTicket(Ticket):
# 親の計算を使ってから、200円を足します。
def fare(self):
return super().fare() + 200
print(NightTicket(600).fare())
# 200 を 300 に変えて、もう一度実行しましょう。
呼ぶ側は種類を調べなくてよい
切符を全部集めて合計するコードを考えます。大人用か子ども用かを、if で調べる必要はありません。
どの切符にも fare() と呼ぶだけで、それぞれの運賃が返ります。これがポリモーフィズムです。新しい種類を足しても、この合計の関数は変わりません。
種類を調べずに合計する
class Ticket:
def __init__(self, base_yen):
self.base_yen = base_yen
def fare(self):
return self.base_yen
class ChildTicket(Ticket):
def fare(self):
return self.base_yen // 2
# 呼ぶ側は、どの種類の切符かを調べません。
def total_fare(tickets):
total = 0
for ticket in tickets:
total = total + ticket.fare()
return total
print(total_fare([Ticket(500), ChildTicket(500), ChildTicket(500)]))
例: 3種類の切符の運賃
| 切符 | 基本の運賃 | fare() の答え | どう決まるか |
|---|---|---|---|
| Ticket | 600円 | 600円 | 親のまま |
| ChildTicket | 600円 | 300円 | 半額に置き換え |
| SeniorTicket | 600円 | 480円 | 親の運賃の80% |
| 3枚の合計 | 1380円 | どの切符にも fare() と呼ぶだけ |
学生が入力するセルは、このプレビューでは空白です。
ラボで3種類の切符を動かす
ラボの端末で、切符のクラスと、合計を出すファイルを作ります。6つの Run を、上から順に押してください。
表の運賃が、本当にその通りに出るかを確かめます。
作業用のフォルダを作る
mkdir -p ~/oop/inheritanceここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
そのフォルダへ移る
cd ~/oop/inheritanceここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
切符のクラスを書く
printf 'class Ticket:\n def __init__(self, base_yen):\n self.base_yen = base_yen\n\n def fare(self):\n return self.base_yen\n\n\nclass ChildTicket(Ticket):\n def fare(self):\n return self.base_yen // 2\n\n\nclass SeniorTicket(Ticket):\n def fare(self):\n return super().fare() * 80 // 100\n' > fares.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
合計を出すファイルを書く
printf 'from fares import Ticket, ChildTicket, SeniorTicket\n\ntickets = [Ticket(600), ChildTicket(600), SeniorTicket(600)]\ntotal = 0\nfor t in tickets:\n print(type(t).__name__, t.fare())\n total = total + t.fare()\nprint("total", total)\n' > trip.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
動かして運賃を見る
python3 trip.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力は表と同じになる
出力は、Ticket 600、ChildTicket 300、SeniorTicket 480、total 1380 の4行です。
ループの中に if はありません。種類ごとの違いは、クラスの中に入っています。
子は親の一種か調べる
python3 -c "from fares import Ticket, ChildTicket; print(isinstance(ChildTicket(600), Ticket), isinstance(Ticket(600), ChildTicket))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
子は親の一種で、逆は成り立たない
出力は True False です。子ども用の切符は、切符の一種です。しかし、切符が子ども用の切符とは限りません。
「子は親の一種だ」と言えるときだけ、継承は正しい形です。これを is-a 関係と呼びます。
よくある間違い
注意
「コードを使い回せるなら、とにかく継承すればよい」と考える人が多くいます。それは間違いです。子が親の一種と言えないのに継承すると、親の変更が思わぬ所を壊します。