[Python] 15. クラスを継承する方法


Study / Python    作成日付 : 2020/06/15 18:20:07   修正日付 : 2020/06/15 18:20:07

こんにちは。明月です。


この投稿はPythonでクラスを継承する方法に関する説明です。


以前の投稿でPythonでクラスを生成して使う方法に関して説明したことがあります。

link - [Python] 12. クラス(Class)を使う方法


Pythonでクラスを生成する時、既存にあるクラスでもっと機能を拡張して使いたい時があります。でも、ただクラスを修正すると既存に参照した領域で影響されるので修正することはできないなのでコピペする時があります。

ただ、コピペすると元のクラスがバグがある時にコピペしたクラスをすべて修正しなければならないです。そのことより、ディベロッパーらしくないコーディングスタイルです。


その時にただコピペでクラスを生成することではなく、継承することで再定義することができます。

# クラス生成
class Main():
  # コンストラクタ
  def __init__(self):
    # アンダーバー(_)二つはprivateだが、一つはprotectedの意味だ。
    self._data = "Main Class"
  # 関数生成
  def exec_function(self):
    # コンソール出力
    print(self._data)

# Main2クラスはMainのクラスを継承した。
class Main2(Main):
  # コンストラクタを再定義
  def __init__(self):
    # 親クラスの_data変数データを定義
    self._data = "Main2 Class"

# Mainインスタンスを生成
a = Main()
# exec_function関数を呼び出す。
a.exec_function()

# Main2インスタンスを生成
b = Main2()
# Main2クラスにはexec_function関数がないがMainクラスを継承したので、使える。
b.exec_function()


上の例をみればMain2クラスは確かにexec_functionがありません。でもMainクラスを継承しましたので、Mainのクラスの機能を使えます。

アンダーバー(_)を一つ使いましたが、protectedという意味で外部では参照できないですが、継承したクラスと内部では参照できるようなアクセス修飾子です。

まとめるとアンダーバーがない場合はpublicですべて参照が可能、アンダーバー(_)が一つで変数名が始まる場合はprotecetdで継承したクラスと内部、アンダーバー(_)が二つで変数名が始まる場合はprivateで内部だけ参照できるような設定です。


つまり、_dataの場合はアンダーバーが一つで変数名が始まるのでMainクラスを継承したMain2で参照ができます。なのでexec_function関数を呼び出すとMainは「Main Class」がMain2は「Main2 Class」が出力されます。

# クラス生成
class Main():
  # コンストラクタ
  def __init__(self):
    # 空欄
    pass
  # get_data関数
  def _get_data(self):
    # strデータを返却
    return "Main Class"
  # exec_function関数で_get_data関数を呼び出す。
  def exec_function(self):
    # コンソール出力
    print(self._get_data())

# Main2クラスはMainのクラスを継承した。
class Main2(Main):
  # Mainクラスで_get_data関数がありますが、再定義する。
  def _get_data(self):
    # strデータを返却
    return "Main2 Class"
# Mainインスタンスを生成
a = Main()
# exec_function関数を呼び出す。
a.exec_function()

# Main2インスタンスを生成
b = Main2()
# Main2クラスにはexec_function関数がないがMainクラスを継承したので、使える。
b.exec_function()


上の例ではMainクラスで_get_data関数を生成しましたが、Main2クラスで継承する時に再定義する構造です。結果はexec_function関数を呼び出すと再定義した_get_data関数を呼び出します。


でも、再定義したが親のクラスを使いたい時があります。

# クラス生成
class Main():
  # コンストラクタ
  def __init__(self):
    # 空欄
    pass
  # get_data関数
  def _get_data(self):
    # strデータを返却
    return "Main Class"
  # exec_function関数で_get_data関数を呼び出す。
  def exec_function(self):
    # コンソール出力
    print(self._get_data())

# Main2クラスはMainのクラスを継承した。
class Main2(Main):
  # Mainクラスで_get_data関数がありますが、再定義する。
  def _get_data(self):
    # strデータを返却
    return "Main2 Class"
  # 関数再定義
  # _get_data関数を再定義したが、親クラスの_get_dataを呼び出す。
  def exec_function(self):
    # コンソール出力
    print(super()._get_data())

# Mainインスタンスを生成
a = Main()
# exec_function関数を呼び出す。
a.exec_function()

# Main2インスタンスを生成
b = Main2()
# exec_function関数を呼び出す。
b.exec_function()


上の例はMain2クラスで_get_dataを再定義しました。でもexec_function関数でMain2の_get_dataではなく、親クラス(super())の_get_data関数を呼び出しました。


今まで、使うクラスを継承して機能をそのままで使うクラスを生成しました。

抽象クラスという定義だけして、機能はないクラスを作ることができます。

# 抽象クラスを生成するためにabcモジュールをインポートする。
from abc import *

# 抽象クラスは括弧の中でmetaclass=ABCMetaというデータを入れる。
class Main(metaclass=ABCMeta):
  # 抽象メソッドはデコレーターabstractmethodを指定する。
  @abstractmethod
  # 関数名を設定する。
  def _get_data(self):
    # 関数機能はなし。 
    pass
  # 関数生成
  def exec_function(self):
    # コンソール出力
    print(self._get_data())

# 抽象クラスを継承したので、_get_data関数を必ず再定義すべきだ。そうではなければエラーが発生する。
class Main2(Main):
  # 関数再定義
  def _get_data(self):
    # strデータをリターン
    return "Main2 Class"
# Main2インスタンスを生成
b = Main2()
# exec_function関数を呼び出す。
b.exec_function()
# 抽象クラスはインスタンス生成だけでエラーが発生する。
a = Main()
# 上でエラーが発生するからこれもできない。
a.exec_function()


Main2クラスは一般クラスなので正常にインスタンスを生成して使うことができます。

Mainクラスは抽象クラスなのでインスタンスを生成ができないです。Main2クラスで継承する時にも抽象メソッドは必ず再定義しなければならないです。それで完全体クラスになることです。


Pythonでは継承は一つだけではなく、複数の継承ができます。

# 抽象クラスを生成するためにabcモジュールをインポートする。
from abc import *

# 抽象クラスは括弧の中でmetaclass=ABCMetaというデータを入れる。
class Main1(metaclass=ABCMeta):
  # 関数生成
  def _get_data1(self):
    # strデータを返却
    return "Main1"
  # 抽象メソッド生成
  @abstractmethod
  def exec_function(self):
    # 関数機能はなし。 
    pass
# 抽象クラスは括弧の中でmetaclass=ABCMetaというデータを入れる。
class Main2(metaclass=ABCMeta):
  # 関数生成
  def _get_data2(self):
    # strデータを返却
    return "Main2"
  # 抽象メソッド生成
  @abstractmethod
  def exec_function(self):
    # 関数機能はなし。
    pass
# Main3にはMain1とMain2のクラスを複数の継承した。
class Main3(Main1, Main2):
  # Main1クラスの関数とMain2クラスの関数を使える。
  def exec_function(self):
    # コンソール出力
    print(self._get_data1())
    # コンソール出力
    print(self._get_data2())
# Main3インスタンスを生成
b = Main3()
# exec_function関数を呼び出す。
b.exec_function()


複数の継承する場合は親のクラスで関数名が重複なる可能性があります。

# 抽象クラスを生成するためにabcモジュールをインポートする。
from abc import *

# 抽象クラスは括弧の中でmetaclass=ABCMetaというデータを入れる。
class Main1(metaclass=ABCMeta):
  # 関数生成
  def _get_data(self):
    # strデータを返却
    return "Main1"
  # 抽象メソッド生成
  @abstractmethod
  def exec_function(self):
    # 関数機能はなし。
    pass
# 抽象クラスは括弧の中でmetaclass=ABCMetaというデータを入れる。
class Main2(metaclass=ABCMeta):
  # 関数生成
  def _get_data(self):
    # strデータを返却
    return "Main2"
  # 抽象メソッド生成
  @abstractmethod
  def exec_function(self):
    # 関数機能はなし。
    pass
# Main3にはMain1とMain2のクラスを複数の継承した。
class Main3(Main1, Main2):
  # 関数生成
  def exec_function(self):
    # Main1クラスとMain2クラスの両方に_get_data関数がある状況
    print(super()._get_data())
# Main3インスタンスを生成
b = Main3()
# exec_function関数を呼び出す。
b.exec_function()


Main3クラスを生成する時にMain1、Main2で継承する順番があります。一番左のクラスが優先で上の状況はMain1とMain2で両方に_get_data関数がある状況ならMain1の_get_dataを呼び出す。

複数継承は様々なクラスを特性を持ってきてよいと思いますが、実際は可読性で悪いので進めるプログラム文法ではないです。

クラスを複数で継承して同じ関数が多いだと思うと呼び出す時にスタック追跡することが大変になります。


ここまでPythonでクラスを継承する方法に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿