クラスの継承

 クラスのオブジェクト指向的な部分でも特徴がある「継承」について説明します。

 クラスの継承の目的

 クラスの継承の目的ついて書かれている文章をみたことが無いので、「継承」という言葉を理解したときの著者が感じたことを話したいと思います。

 プログラムと言わず、何か工作で作成したとします。取り敢えず作ることができましたが、ちょっと改良したくなったとします。さて次のうちどうしたらいいでしょう。
 1.一から作り直す。2.改良したい部分だけ作り直す。
 一から作り直したのでは、お金や時間がかかります。改良したい部分だけ作り直した方が早いでしょうが、作り直そうとして、今までうまく行っていたところが壊れてしまってしまうかも知れません。そうしたら、また作り直すことと同じになってしまいます。ということで、どちらもリスクがあります。
 そこでプログラム独自の第3の回答、ファイルをコピーしてそれを編集する。そうすれば失敗しても、もう一回やり直すことができます。その考えの延長で、元の部分をそのままに書き換えず、クラスのコピーを作成して変更したい部分を更新する記述をすれば、間違えることが少なくなります。
 
 このようにクラスをコピーして変更点を更新し新しいクラスを作ることを「継承」と言います。

classの定義文は次のように書きます。

class 新規クラス名(継承元クラス名):
 継承元クラス名は省略する場合は『object』というクラスを継承することになります。
省略することは可能ですが、省略するといろいろ新しい機能が使えないという問題が発生することがあります。省略せず、『object』を記述することをお勧めします。
ちなみに新規クラスのことをサブクラス、継承元クラスをスーパークラスというので
classの定義文は次のように書かれています。

class サブクラス名(スーパークラス名):

# coding: shift-jis
# クラスの定義をします。
class Parent(object):
  def set_x(self, number):
    self.x = number

  def show_x(self):
    print self.x

class Child(Parent):
  def set_y(self, number):
  self.y = number

def show_y(self):
  print self.y

# 以下実行文です。
ob = Child()
ob.set_x(10)←Childクラスではアトリビューxは定義していませんが、
ob.set_y(20) 継承しているので使えます。
ob.show_x()
ob.show_y()

raw_input()

出力画面:
 10
 20
 Childクラスではアトリビュートxを定義していませんがParentを継承しているためにset_xメゾットやshow_xメゾットが使用できます。
 この例は、継承をすることによってChildクラスで新しいメゾット(機能)を追加したものです。次は機能を改善するオーバーライドというものをやって見ましょう。

 オーバーライドをやってみよう

 オーバーライドとは継承元のメゾットと同じ名前を使って定義して、メゾットを改善することです。

# coding: shift-jis
# クラスの定義をします。

class Parent(object):
  def set_x(self, number):
    self.x = number

  def show_x(self):
    print self.x


class Child(Parent):
  def set_y(self, number):
    self.y = number

  def show_x(self):
    print "X =", self.x

  def show_y(self):
    print "Y =", self.y

# 以下実行文です。
ob = Child()
ob.set_x(10)
ob.set_y(20)
ob.show_x()
ob.show_y()

raw_input()

出力画面:
 X = 10
 Y = 20
 上の例ではParentクラスのshow_xメゾットではxの値を表示するだけですが、Childクラスでは「X =」と表示するように改善されています。

 オーバーライドをすることで元のクラスを全く変更しなくても改善されていることが分かりますか?元のコードを変更しないため、間違えが少なくなるのですが、逆に面倒くさいことが起きることがあります。つまり、せっかく元のクラスで定義したメゾットをまた再度定義しなければいけなくなります。それを解決する例を説明します。

# coding: shift-jis
#クラスの定義をする
class Parent(object):
  def __init__(self, number):
    print "親のイニシャライズ"
    self.set_x(number)

  def set_x(self, number):
    self.x = number

  def show_x(self):
    print self.x


class Child(Parent):
  def __init__(self, number):
    Parent.__init__(self, number)←Parentクラスの初期化メゾットを呼び出す。
    print "子のイニシャライズ"

  def set_y(self, number):
    self.y = number

  def show_y(self):
    print self.y

#以下は実行文です。
ob = Child(10)←Childクラスの初期化でset_xを実行しています。
ob.set_y(20)
ob.show_x()
ob.show_y()

raw_input()

raw_input()

出力画面:
 親のイニシャライズ
 子のイニシャライズ
 10
 20
 Childクラスの中でParentクラスのメゾットを呼び出しています。例として無理やり使っていますので、かなり不細工なコードなっていますが、こんな風にして、スーパークラスの定義を呼び出すことができます。更にsuper関数を使って書くと次のようにも書けます。

# coding: shift-jis

class Parent(object):
  def __init__(self, number):
    print "親のイニシャライズ"
    self.set_x(number)

  def set_x(self, number):
    self.x = number

  def show_x(self):
    print self.x


class Child(Parent):
  def __init__(self, number):
    super(self.__class__, self).__init__(number)
    print "子のイニシャライズ"

  def set_y(self, number):
    self.y = number

  def show_y(self):
    print self.y

ob = Child(10)
ob.set_y(20)
ob.show_x()
ob.show_y()

raw_input()

出力画面は前と同じなので割愛します。

   super(self.__class__, self).__init__(number)

上記コードの意味がなんとなくわかりますか?self.__class__はサブクラス名のChildに置き換えることができます。Childクラスのスーパークラスの__init__メゾットをselfとnumberを引数にして呼び出しています。

 クラスの多重継承

 Iron Pythonのクラスは多重継承を行うことができます。多重継承とは読んで字のごとく複数のクラスから継承して、いいとこ取りするクラスを作ることができます。classの定義文は次のように書かれています。

class サブクラス名(スーパークラス名[,スーパークラス名・・・]):

 使い方は次のようにします。

# coding: shift-jis

#クラスの定義
class Other(object):
  def set_z(self, number):
    self.z = number

  def show_z(self):
    print "Z =",self.z

class Parent(object):
  def set_x(self, number):
    self.x = number

  def show_x(self):
    print "X =",self.x


class Child(Parent, Other):←ParentとOtherを多重継承
  def set_y(self, number):
    self.y = number

  def show_y(self):
    print "Y =",self.y

#実行文
ob = Child()
ob.set_x(10)
ob.set_y(20)
ob.set_z(30) ←Otherも継承しているので、set_z関数も使えます。
ob.show_x()
ob.show_y()
ob.show_z()

raw_input()

出力画面:
 X = 10
 Y = 20
 Z = 30
 上でいいとこ取りした多重継承と書きましたが、実は『多重継承』すごく難しいのです。例えば、ParentとOtherに同じ関数名が定義されていた時Childで実行されると、どちらが実行されるでしょう。試してみれば分かります。

# coding: shift-jis
#クラスの定義

class Other(object):
  def set_x(self, number):
    print "Otherのset_xが実行されました"
    self.x = number

  def show_x(self):
    print "Otherのshow_xが実行されました"
    print "X =",self.x

class Parent(object):
  def set_x(self, number):
    print "Parentのset_xが実行されました"
    self.x = number

def show_x(self):
    print "Parentのshow_xが実行されました"
    print "X =",self.x


class Child(Parent, Other):
  def set_y(self, number):
    self.y = number

  def show_y(self):
    print "Y =",self.y

#実行文
ob = Child()
ob.set_x(10)
ob.set_y(20)
ob.show_x()
ob.show_y()

raw_input()

出力画面:
 Parentのset_xが実行されました
 Parentのshow_xが実行されました
 X = 10
 Y = 20

 Parentが実行されました。何故Parentが実行されたか分かりますか?Parentのクラス定義がOtherより後だったから?Childの定義文のスーパークラスがOtherより先だったから?
 調べてみるとChildの定義文のスーパークラスがOtherより先だったから、と分かりましたが、これってやってみないと分からないことですよね。つまりあいまいなんです。
あいまいというのは問題で、決まっていないのでどう動くか決まっていないことを意味します。このように『多重継承』はうまく動かない危険性を持ち合わせています。
 同じように多重継承が使えるC++言語でも、多重継承はできるだけ使わないほうがよいと説明していることが多いです。『多重継承』は一見便利なことが多いようですが使い方には注意しましょう。