【Python】クラスの継承。機能を引き継いで新しいクラスを作る【入門第31回】
目次
クラスの継承とは?
こんにちは、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
このように継承を使うと、簡単にコードを共通化して、無駄なコードを減らすことができます。
しかし、これは皆さんにこっそりお教えするのですが、共通化というのはプログラミングにおいてかなり難しい作業です。
どういうことかと言うと、共通化は失敗する場合がけっこうあるのです。
継承で共通化が簡単にできるからと言って、なんでもかんでも共通化するのはよくありません。
共通化を行う場合は、時間をかけて検証をして、石橋を叩くぐらいの気でやりましょう。
おわりに
継承は便利な反面、ちゃんと扱えるようになるまで時間がかかる機能でもあり、奥が深いものです。
みなさんもいろいろな実験をして、クラスと継承について検討してみてください。
ひと昔前はこういった設計の問題はデザイン・パターンを学ぶと良いとされてましたが、最近ではあまりぱっとしていないようです。
むしろ、継承は設計を複雑にするので、なるべく使わないようにしようという流れもあります。
しかし、大規模なフレームワーク等では依然として継承がよく使われています。
正解がないのね
そのとおり、正解がないのです。
つまり、自分の頭で考えて取捨選択して使わなければいけません。
以上、次回に続きます。
また見てね