【Python】クラスの継承。機能を引き継いで新しいクラスを作る【入門第31回】

126, 2019-08-25

目次

クラスの継承とは?

こんにちは、narupoです。

今回はクラスの継承を紹介したいと思います。
継承はクラスの特徴ともいえる機能です。
継承を使うと、親クラスの機能を子クラスが引き継げるようになります

具体的には↓を見ていきます。

  • 継承の使い方

  • メソッドの継承

  • メソッドのオーバーライド

  • __init__の呼び出し

  • 継承を使ったコードの共通化

この継承という機能は、ちゃんと扱えるようになるにはけっこう練習が必要なんですが、扱えるようになるとたいへん便利なので、この記事で覚えて使えるようになりましょう。

継承の使い方

さきほど、継承を使うと、親クラスの機能を子クラスが引き継げるようになると書きました。
これはいったいどういうことなのでしょうか。

たとえば↓のようなクラスがあるとします。

:::python
class Animal:
    pass

このAnimalクラスを親として、Animalクラスの機能を引き継いだクラスを作りたい場合、↓のようにします。

:::python
class Cat(Animal):
    pass

クラス名Catのあとにカッコ(())を付けて、そこに継承したいクラス名を書きます。
この例では、CatクラスにAnimalクラスを継承させたいので、Animalと書いています。
これでCatクラスのAnimalクラスからの継承が完了しました。
このとき、Animalクラスを親クラス、Catクラスを子クラスと言います。

メソッドの継承

先ほどのAnimalクラスは空っぽでしたが、たとえば↓のようにsayというメソッドを作っておきます。
そして先ほどと同じようにCatクラスにAnimalクラスを継承させます。

:::python
class Animal:
    def say(self):
        print('ガー!')


class Cat(Animal):
    pass

このCatクラスはAnimalクラスの機能を引き継いでいるので、Animalクラスのメソッドsayを呼び出すことができます。
Catクラスからインスタンスを作ってやってみましょう。

:::python
cat = Cat()
cat.say()

↑のコードの実行結果は↓のようになります。

ガー!

Animalクラスのsayメソッドを呼び出すことが出来ました。
このとき、気になるのがsayメソッドのselfの値です。

:::python
class Animal:
    def say(self):
        print('ガー!')

↑のsayメソッドが先ほどのインスタンスcatから呼ばれた場合、selfは何の値になるかという話です。
実はこれは、Catクラスのインスタンスになります。
ためしにsayメソッド内で↓のようにselfの型を出力してみましょう。

:::python
class Animal:
    def say(self):
        print(type(self))

↑のようにsayメソッドを編集して先ほどのコードを実行すると↓のような結果になります。

<class '__main__.Cat'>

selfの型がCatになっているのがわかります。
このように、継承先(Cat)で継承元(Animal)のメソッドを呼び出すと、メソッドのselfは継承先(Cat)のインスタンスになります。

メソッドのオーバーライド

筆で上書き

先ほどはAnimalクラスのメソッドsayを使いましたが、このsayメソッドが気に入らないので改造したくなったとします。
そういう場合はメソッドをオーバーライド(上書き)します。

:::python
class Animal:
    def say(self):
        print('ガー!')


class Cat(Animal):
    pass

↑の場合で、CatクラスでAnimalクラスのsayメソッドを上書きしたい場合はCatクラスを↓のようにします。

:::python
class Cat(Animal):
    def say(self):
        print('にゃーん')

↑のようにオーバーライドしたい同名のメソッドを作れば、上書き完了です。
インスタンスを作ってsayメソッドを呼び出してみましょう。

:::python
cat = Cat()
cat.say()

↑のコードの実行結果は↓のようになります。

にゃーん

オーバーライドしたメソッド内で親クラスの同名メソッドを呼び出したい場合もあります。
今回の例ではCatクラスのsayメソッド内から、Animalクラスのsayメソッドを呼び出したい場合です。
そういうときは↓のようにsuperクラスを使います。

:::python
class Cat(Animal):
    def say(self):
        super().say()

super()を実行すると親クラスのインスタンスが返ってきます(正確にはプロキシオブジェクトというものが返ってきます)。
親クラスのインスタンスを得ることが出来たので、そのインスタンスのメソッドsayを呼び出します。
先ほどのようにコードを実行すると↓のような結果になります。

ガー!

Catクラス内で親クラスのAnimalクラスのメソッドsayを呼び出せていますね。

__init__の呼び出し

__init__メソッドを作って、クラスのインスタンス変数を設定したとします。
たとえば↓のようにです。

:::python
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

↑のAnimalクラスを継承してCatクラスを作りました。

:::python
class Cat(Animal):
    pass

そしてCatをインスタンス化して↓のようにインスタンス変数を参照します。

:::python
cat = Cat('五郎', 3)
print(cat.name, cat.age)

↑のコードの実行結果は↓のようになります。

五郎 3

ちゃんとAnimalクラスの__init__メソッドが呼ばれているようです。
それでは、Catクラスで__init__メソッドをオーバーライドした場合はどうなるでしょうか?
たとえば↓のようにです。

:::python
class Cat(Animal):
    def __init__(self, name, age):
        pass

↑のようにCatクラスを改造して、先ほどのコードを実行すると、

:::python
cat = Cat('五郎', 3)
print(cat.name, cat.age)

↓のようになります。

AttributeError: 'Cat' object has no attribute 'name'

インスタンス変数がうまく作られていないようです。なぜでしょうか?
これは、オーバーライドが原因です。
Catクラスで__init__メソッドをオーバーライドしましたが、メソッドをオーバーライドすると親クラスのメソッドは呼ばれません。
よって、親のAnimalクラスの__init__メソッドが呼ばれないので、インスタンス変数が初期化されないのです。
この問題を解決するには↓のようにsuperクラスを使います。

:::python
class Cat(Animal):
    def __init__(self, name, age):
        super().__init__(name, age)

super()で親のインスタンスをゲットして__init__メソッドを呼び出します。
この状態で先ほどのコード、

:::python
cat = Cat('五郎', 3)
print(cat.name, cat.age)

↑を実行すると↓のような結果になります。

五郎 3

インスタンス変数がAnimalクラスの__init__メソッドによって初期化されました。

継承を使ったコードの共通化

一卵性双生児

継承を使ったプログラミングでありがちなのが、継承を使ったコードの共通化です。
例えば↓のようなクラスがあるとします。

:::python
class Cat:
    def run(self):
        print('走ります!')


class Dog:
    def run(self):
        print('走ります!')    

↑のCatクラスとDogクラスのメソッドrunは、機能的には同じものです。
しかし、メソッドが2つあって何だか無駄に見えます。
こういったときに継承を使うと、たとえば↓のようにコードを共通化することができます。

:::python
class Animal:
    def run(self):
        print('走ります!')


class Cat(Animal):
    pass


class Dog(Animal):
    pass

このように継承を使うと、簡単にコードを共通化して、無駄なコードを減らすことができます。
しかし、これは皆さんにこっそりお教えするのですが、共通化というのはプログラミングにおいてかなり難しい作業です。
どういうことかと言うと、共通化は失敗する場合がけっこうあるのです。
継承で共通化が簡単にできるからと言って、なんでもかんでも共通化するのはよくありません
共通化を行う場合は、時間をかけて検証をして、石橋を叩くぐらいの気でやりましょう。

おわりに

継承は便利な反面、ちゃんと扱えるようになるまで時間がかかる機能でもあり、奥が深いものです。
みなさんもいろいろな実験をして、クラスと継承について検討してみてください。
ひと昔前はこういった設計の問題はデザイン・パターンを学ぶと良いとされてましたが、最近ではあまりぱっとしていないようです。
むしろ、継承は設計を複雑にするので、なるべく使わないようにしようという流れもあります。
しかし、大規模なフレームワーク等では依然として継承がよく使われています。

正解がないのね

そのとおり、正解がないのです
つまり、自分の頭で考えて取捨選択して使わなければいけません。

以上、次回に続きます。

また見てね

関連動画