概要
たまに間違えてバグ作ってしまったりしてるので、忘れないようメモメモ。
python で サブクラスの __init__
定義する場合に
super().__init__()
としていないと、親クラスの__init__()
が呼ばれないよって話しです。
他の言語の場合 (例えば C#)
C#
の場合
using System; namespace ConsoleApplication1 { internal class BaseClass { internal int BaseValue { get; } public BaseClass() { BaseValue = 10; } } internal class DerivedClass : BaseClass { internal int DerivedValue { get; } public DerivedClass() { DerivedValue = 20; } } internal class Program { public static void Main(string[] args) { var x = new DerivedClass(); Console.WriteLine($"Base={x.BaseValue}, Derived={x.DerivedValue}"); } } }
と記述して、実行すると
Base=10, Derived=20
って表示されます。つまり、サブクラス側で明示的に親クラスのコンストラクタを
呼んでなくても、この場合は暗黙で デフォルトコンストラクタ が呼ばれます。
python の場合
python の場合、__init__
メソッドは厳密にはコンストラクタではないのですが
まあ、大体同じものだということで。以下のようなクラスを定義してみて
class B: """ ベースクラス """ def __init__(self) -> None: self.x = 10 class C(B): """ サブクラス1 自分の __init__ を定義していないバージョン """ pass
使ってみると
obj1 = C()
pr('C.x', obj1.x)
以下のように表示されます。
C.x=10
予想通りの動作。次に
class D(B): """ サブクラス2 自分の __init__ を定義しているが super().__init__() を呼んでいないバージョン """ # noinspection PyMissingConstructor def __init__(self): self.y = 20
を定義して
obj2 = D() pr('D.x', obj2.x) pr('D.y', obj2.y)
ってすると
AttributeError("'D' object has no attribute 'x'",)
となります。サブクラス側が __init__
を定義しているので
親クラス側の __init__
がオーバーライドされます。 python は素直な言語なので
普通に上書きされます。なので、この場合 親クラスの__init__
が呼ばれない となります。
で、以下のようにするのが正しいやり方。
class E(B): """ サブクラス3 自分の __init__ を定義していて super().__init__() も呼んでいるバージョン """ def __init__(self): super().__init__() self.y = 20
呼び出すと
obj3 = E() pr('E.x', obj3.x) pr('E.y', obj3.y)
ちゃんと表示されます。
E.x=10 E.y=20
まとめ
てことで、サブクラスで __init__
定義するときは、必ず
super().__init__()
を呼び出しするべしってなります。
PyCharmのような統合環境使っている場合は自動で挿入してくれていたり
呼び出しを書いていなかった場合は警告が出たりしますので、呼び出し忘れることは
ほぼ無いのですが、エディタで書いてたりすると抜けてたりします。(しましたw
実行すると意味の分からないエラーパターンになるので焦ります。。
ちなみに、このパターンは __init__
だけじゃなくて、どのメソッドでも当てはまります。
サンプル
""" Python のクラスについてのサンプルです。 サブクラス側で __init__ を定義した場合の注意点について。 """ from common.commoncls import SampleBase from common.commonfunc import pr, hr class B: """ ベースクラス """ def __init__(self) -> None: self.x = 10 class C(B): """ サブクラス1 自分の __init__ を定義していないバージョン """ pass class D(B): """ サブクラス2 自分の __init__ を定義しているが super().__init__() を呼んでいないバージョン """ # noinspection PyMissingConstructor def __init__(self): self.y = 20 class E(B): """ サブクラス3 自分の __init__ を定義していて super().__init__() も呼んでいるバージョン """ def __init__(self): super().__init__() self.y = 20 class Sample(SampleBase): """ サンプルとなるクラスです。 """ def exec(self): """ 処理を実行します。 """ # ------------------------------------------------------------ # (1) サブクラス側が __init__ を定義していない場合 # 既定で、 super().__init__() が呼ばれた状態となる # ------------------------------------------------------------ obj1 = C() pr('C.x', obj1.x) hr() # ------------------------------------------------------------ # (2) サブクラス側が __init__ を定義しているが super().__init__() を # 呼んでいない場合、親クラスの __init__ は呼ばれない。 # # 他の言語に慣れている場合、デフォルトのコンストラクタが呼ばれるのが暗黙的なので # よく間違えてしまう。注意が必要。 # # PyCharmで作業している場合、IDE側が警告を出してくれるので気付ける。 # ------------------------------------------------------------ try: obj2 = D() pr('D.x', obj2.x) pr('D.y', obj2.y) except AttributeError as e: pr('D', e) hr() # ------------------------------------------------------------ # (3) サブクラス側が __init__ を定義していて super().__init__() を # 呼んでいる場合、親クラスの __init__ も呼ばれる。 # ------------------------------------------------------------ obj3 = E() pr('E.x', obj3.x) pr('E.y', obj3.y) def go() -> None: """ サンプルを実行します。 """ obj = Sample() obj.exec() if __name__ == '__main__': go()
try-python/cls03.py at master · devlights/try-python · GitHub
補足
この点については、書籍「Python in a nutshell」でも記載されていました。
- 作者: Alex Martelli,Anna Ravenscroft,Steve Holden
- 出版社/メーカー: Oreilly & Associates Inc
- 発売日: 2017/05/04
- メディア: ペーパーバック
- この商品を含むブログを見る
書籍内では、
単純に親クラスの処理をデリゲートするだけのメソッドを書いてはいけません。 (Never code a method that just delegates to the superclass)
つまり、以下のようなものは定義したらダメだよってことですね。
class Derived(Base): def __init__(self): super().__init__()
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場