ここからはクラスの応用について学習します。
クラスは型や設計図で実体がなく、実体であるインスタンスのほうが大事と感じるかもしれませんが、実はクラスの設計こそ重要であり、オブジェクト指向プログラミングのおもしろいところです。
クラスの継承
オブジェクト指向を構成する概念の一つに継承があります。あるオブジェクトをベースに新たなオブジェクトを生成する、プログラムの再利用性を高める考え方です。
Pythonでは、すでに定義されたクラスの機能を引き継いで、新しいクラスの定義ができ、これをクラスの継承といいます。このとき、継承元になったクラスを親クラスまたはスーパークラスや基底クラスと呼び、新たに定義されたクラスは子クラスまたはサブクラスや派生クラスなどと呼びます。本教材では親クラス・子クラスで統一します。
クラスの継承では、基本的な機能は元の親クラスから引き継ぎ、子クラスで必要な部分だけ書き換えたり、新たに機能を追加したりできます。これによって、クラスの拡張が容易になり、元になる親クラスの保守性も上がります。クラスを継承するには、継承したい親クラス名を引数として指定します。
####【書式】子クラスのクラス定義
class 子クラス名(親クラス名):
クラスの内容
では、簡単に親クラスと子クラスを定義し、クラスの継承をプログラムで確認しましょう。
# 親クラスを定義する
class Human:
def __init__(self, name, age):
self.name = name
self.age = age
def status(self):
print("氏名:" + self.name, "年齢:" + str(self.age) + "歳")
# 親クラスを継承した子クラスを定義する
class Human_say(Human):
def introduce_myself(self):
print("私は" + self.name + "です。" + str(self.age) + "歳です。")
# 子クラスのインスタンスを生成する
a_man = Human_say("荒木", 58)
# 親クラスのメソッドを呼び出す
a_man.status()
# 子クラスのメソッドを呼び出す
a_man.introduce_myself()
実行してみましょう。
氏名:荒木 年齢:58歳
私は荒木です。58歳です。
子クラスで生成されたインスタンスに親クラスのメソッドが引き継がれ、子クラスと親クラス両方のメソッドが実行できます。このように、クラスを継承することで簡単に機能を拡張していくことができます。
メソッドのオーバーライド
クラス継承の際、親クラスで定義されているメソッドと同名のメソッドを定義することによって、メソッドを上書きすることができます。これをメソッドのオーバーライドと言います。
先ほどのプログラムの親クラスで定義されているstatus()
メソッドを、オーバーライドしてみましょう。
class Human:
def __init__(self, name, age):
self.name = name
self.age = age
def status(self):
print("氏名:" + self.name, "年齢:" + str(self.age) + "歳")
class English_Human(Human):
# status()メソッドをオーバーライドする
def status(self):
print("NAME:" + self.name, "AGE:" + str(self.age))
# 親クラスのインスタンスを生成する
a_man = Human("荒木", 58)
# 子クラスのインスタンスを生成する
a_eng_man = English_Human("Araki", 58)
# メソッドを呼び出す
a_man.status()
a_eng_man.status()
実行してみましょう。
氏名:荒木 年齢:58歳
NAME:Araki AGE:58
親クラスと子クラスで生成されたインスタンスには、それぞれ同名のstatus()
メソッドを実行していますが、子クラスはオーバーライドによって表示内容が変更されています。
継承は、同じようなプログラムをまとめて再利用性を高める考え方ですが、それだけでは似た振る舞いのプログラムしか作れないことになります。オーバーライドを利用することで、継承によって共通化する部分は再利用しつつ、違う振る舞いにしたい部分は目的に合わせて変えることができます。
ポリモーフィズムとは
このように、同一のメソッド名で属するクラスによって異なる処理が行われる性質をポリモーフィズム(多態性・多相性)といい、オブジェクト指向において重要な考え方の一つです。
~~継承は、同じようなプログラムをまとめて再利用性を高める考え方ですが、それだけでは似た振る舞いのプログラムしか作れないことになります。ポリモーフィズムは、継承によって共通化する部分は再利用しつつ、違う振る舞いにしたい部分は目的に合わせて変えられる、オーバーライドを利用したものです。~~
例えば、テレビのリモコンは異なったメーカーのものでも、基本的な操作方法は共通しています。実際に内部で行われる処理はリモコンやテレビによって異なりますが、ユーザーは初めて触れるリモコンでも、数字のボタンでチャンネルを換えたり、ボリュームボタンを上に押せば音量を大きくできることがわかります。
つまり、異なるオブジェクト間に共通のメソッドを用意しておくことで、ユーザーはオブジェクトの内部を意識せずに**「この処理をするにはこのメソッドを使う」ことがわかり、オブジェクトによって処理内容を変化できることがポリモーフィズム**です。これは、ユーザーだけでなくプログラミングにおいても、メソッド名を統一することで、名前を覚える必要がなくなること、記述ミスを減らせること、などが利点として挙げられます。
親クラスのメソッドを参照
ただし、意図せずメソッドの上書きをしてしまい、プログラムにエラーが発生することもあり、オーバーライドには十分注意が必要です。特に、親・子クラスともに初期化メソッドを使う場合、親クラスの初期値が上書きされ、値が参照できないことがあります。
組み込み関数のsuper()
を使うことで、オーバーライドによって上書きされた親クラスのメソッドを呼び出すことができます。
####【書式】super()関数で親クラスのメソッドを参照する
super().親クラスのメソッド
このとき、参照する親クラスのメソッドには第1引数のself
は不要なので注意してください。
では、実際にsuper()
関数で親クラスのメソッドを参照して、子クラスのメソッドを定義しましょう。
class Human:
def __init__(self, name, age):
self.name = name
self.age = age
def status(self):
print("氏名:" + self.name, "年齢:" + str(self.age) + "歳")
class Human2(Human):
# 初期化メソッドをオーバーライドする
def __init__(self, name, age, blood, job):
# 親クラスの初期化メソッドを参照する
super().__init__(name,age)
# 変数を追加する
self.blood = blood
self.job = job
# status()メソッドをオーバーライドする
def status(self):
# 親クラスのstatus()メソッドを参照する
super().status()
# status()メソッドに追加する
print("血液型:" + self.blood,"職業:" + self.job)
a_man = Human2("荒木", 58, "B", "漫画家")
a_man.status()
実行してみましょう。
氏名:荒木 年齢:58歳
血液型:B 職業:漫画家
親クラスの初期化メソッドとsuper()
メソッドを参照し、子クラスではさらに独自のデータとメソッド内容を追加できました。
今回の例のように、小規模なプログラムではわざわざsuper()
関数で親クラスを参照せず、同じ内容をオーバーライドで書き直したほうがラクに思われるかもしれませんが、このように記述することで、子クラスでは何を追加しようとしたのかがわかります。継承したメソッドをオーバーライドする場合、継承する部分とオーバーライドする部分を明示的に記述するようにしましょう。
多重継承
Pythonでは複数のクラスから継承することも可能です。これを多重継承といいます。多重継承するには、継承したい複数の親クラス名を,
(カンマ)で区切って指定します。
####【書式】多重継承によるクラス定義
class 子クラス名(親クラス名1, 親クラス名2, 親クラス名3 ... ):
クラスの内容
では、簡単な多重継承の例を見てみましょう。
以下は、A、Bというクラスを定義して、多重継承によってA、Bのメソッドを持つクラスCを定義しています。
class A:
def sayA(self):
print("Aクラス")
class B:
def sayB(self):
print("Bクラス")
# A,Bクラスを継承するCクラスを定義する
class C(A, B):
pass
c = C()
c.sayA()
c.sayB()
実行してみましょう。
Aクラス
Bクラス
Cクラスから生成されたインスタンスに、クラスA、Bで定義されたメソッドが継承され、実行できました。
多重継承の優先順位
クラスがさらに増えるとどうなるでしょうか。次の例を見てみましょう。
class A:
name = "Aクラス"
class B:
name = "Bクラス"
class C:
name = "Cクラス"
class D:
name = "Dクラス"
class E(A,B):
def say_name(self):
print(self.name)
print("Eクラスのメソッド")
class F(C,D):
def say_name(self):
print(self.name)
print("Fクラスのメソッド")
class G(E,F):
pass
g = G()
g.say_name()
クラスは多重継承が可能ですが、当然、多重継承したクラスをさらに多重継承、ということもできます。また、継承で同名の変数やメソッドがある場合は上書き(オーバーライド)されます。
しかし、この例のように多重継承によって変数やメソッドの名前がぶつかった場合、どちらが優先されるでしょうか?プログラムを実行して確認してみましょう。
Aクラス
Eクラスのメソッド
Pythonでは、多重継承によって名前がぶつかった場合、多重継承に指定したクラスの順番で優先されます。つまり、指定した左側のクラスが優先されるため、say_name()
メソッドはEクラス、メソッドが参照している変数name
はE→Aクラスのものを読み込みます。
もし、Gクラスがclass G(E,F)
ではなくclass G(F,E)
と、多重継承するクラス名を逆に記述した場合はどうなるでしょうか?今度は、Fクラスが優先されるためsay_name()
メソッドはFクラス、変数name
はF→Cクラスに読み込みます。もし、Cクラスに変数name
が存在しない場合は、Dクラスを参照します。
このように多重継承は指定したクラス名の記述順で優先順位が決まるため、注意が必要です。
多重継承の問題点
一見すると、多重継承はとても自由度が高く便利な機能ですが、先ほどの例のように、多重継承の多重継承というクラスを作ってしまうと、構造が複雑化し参照元のメソッドや変数の把握が難しくなります。
また、名前の衝突によって意図しない上書きも起こりやすく、予期しないエラーを招く危険性があります。
これを回避するためにも、わかりやすい名前付けやsuper()
関数の利用は有効ですが、何よりも多重継承が招く問題を把握し、必要最小限度の利用を心がけるようにしましょう。
初学者のためのPython講座 オブジェクト指向編1 オブジェクト指向とは
初学者のためのPython講座 オブジェクト指向編2 クラスとインスタンス
初学者のためのPython講座 オブジェクト指向編3 メソッド
初学者のためのPython講座 オブジェクト指向編4 初期化メソッド
随時追加予定