Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

pythonで継承とsuper()を使って派生クラスをinitしてみよう

次のようなクラスを作りたいと考えたとします。

  • 親クラスのクラス変数に5を持つ
  • 親クラスのクラスメソッドでは、親子両方のクラス変数を使って何か適当な計算を行い、その結果を返す
    • 適当な計算の中身は、親クラス変数の5と子クラス変数のxを足してその和に2をかける、というものにしました。
    • 今回、親クラスのインスタンス化については考えないことにします。

まずは親クラスのクラス変数と、そのクラスメソッドを書いてみます。

動作確認してません.py
class Parent:
  def __init__(self, child_num):
    self._parent_num = 5
    self._child_num = child_num

  def calc(self):
    result = (self._parent_num + self._child_num) * 2
    return result

子クラスについても書いてみます。子クラスのクラス変数は仮に34にしてみましょう。
継承をすると、親クラスと同じメソッドは子クラスで宣言しなくても使えるので、calcメソッドの定義は必要なさそうです。
しかし、34については親との差分なので、次のような記述が必要になるかもしれません。

動作確認してません.py
class Child_A(Parent):
  def __init__(self):
    self._child_num = 3

class Child_B(Parent):
  def __init__(self):
    self._child_num = 4

ところが、これでChild_A().calc()を実行するとエラーになってしまいます。
エラーの原因はself._parent_numが定義されていないというものになるはずです。

親で定義されているはずなのに、定義されていないなんてエラーが出るのはなぜ?
これは、子で改めて__init__を定義したせいで、親の__init__が上書きされてしまったからなんですね。
self._parent_numを定義しているのはあくまでも親の__init__ですので、子のほうで上書きしてしまうと未定義のままになってしまうわけです。

となると、次のように書くしかないのでしょうか?

動作確認してません.py
class Child_A(Parent):
  def __init__(self):
    self._parent_num = 5 # この同じ内容を何度も書かなければいけないのか?
    self._child_num = 3

class Child_B(Parent):
  def __init__(self):
    self._parent_num = 5 # この同じ内容を何度も書かなければいけないのか?
    self._child_num = 4

この書き方であればエラーは起こらないでしょうが、あまり良い方法とは言えません。
子クラスが何十個も必要になったり、あとからself._parent_numの値を変えたくなったり、そもそも親クラスのクラス変数がたくさん定義されているような場合になると目がくらんでくるでしょう。

そこで役に立つのがsuper()というわけです。
公式ドキュメントの記述はこちらになります。
super([type[, object-or-type]])
しかし、公式の内容はあまりに網羅的・あまりに詳細なので、パッと理解するにはいささか複雑すぎます(もちろん、しっかりとした理解を得るためにはそういった記述が必要になります)。
ここでは、現在問題にしているケースでの使い方に絞って見ていくことにします。

super()とは、少なくともここで問題にしているケースにおいては、親クラスのメソッドを実行してくれるものとして役に立ってくれます。
次のようなクラス定義について考えてみましょう。

動作確認してません.py
class Child_A(Parent):
  def __init__(self):
    super().__init__(3)

とても不思議なクラス定義が現れました。
しかし、しっかり読んでみれば話は簡単です。
super()とは、少なくともここでは、親クラスのメソッドを実行してくれるものとして役に立ってくれるものでした。
そのため、子クラスの中で実行したsuper().__init__(3)というコードは、親クラスのdef __init__(self, child_num)の引数child_numに対して3を渡してインスタンス化するという処理を実行してくれるコードとなっているのです。
結果としてChild_Aクラスの中の__init__そのものは親クラスとは異なる(上書きされた)内容となっていますが、その中では親クラスの__init__と全く同じ処理を通ってくれています。
それゆえ、super()を使わなかった場合に未定義となっていたself._parent_numChild_Aの中からしっかりと参照できますし、self._child_numの値にも3が代入されている状態で利用することができます。

従って、子を子をいくつも作りたい場合は次のようなコードを書くと幸せになれるでしょう。

動作確認してません.py
class Parent(object): # object型を継承
  def __init__(self, child_num):
    self._parent_num = 5
    self._child_num = child_num

  def calc(self):
    result = (self._parente_num + self._child_num) * 2
    return result

class Child_A(Parent):
  def __init__(self):
    super().__init__(3)

class Child_B(Parent):
  def __init__(self):
    super().__init__(4)

おや!注意深い方は、親クラスの方にも何か変更が加えられているのに気が付くでしょう。
実は、super()を使って親クラスのメソッドを実行したいなら、親クラスはobject型を継承している必要があります。
最初のコードではobject型を継承するように書いていませんでした。例えの説明に必要なかったので。
実装してみるときはお気を付けください。それでは。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away