多重継承のシナリオがあるとします。
class A(object):
# code for A here
class B(object):
# code for B here
class C(A, B):
def __init__(self):
# What's the right code to write here to ensure
# A.__init__ and B.__init__ get called?
C
の書き方には、2 つの典型的なアプローチがあります__init__
。
- (旧式)
ParentClass.__init__(self)
- (新しいスタイル)
super(DerivedClass, self).__init__()
しかし、どちらの場合でも、親クラス(A
およびB
)同じ規則に従わないと、コードは正しく動作しません。(一部は見逃されたり、複数回呼び出されたりする場合があります)。
では、正しい方法は何でしょうか? 「一貫性を保ち、どちらか一方に従う」と言うのは簡単ですが、 またはA
がB
サードパーティのライブラリからのものである場合はどうでしょうか? すべての親クラスのコンストラクターが呼び出されるようにする (正しい順序で、1 回だけ) 方法はありますか?
編集: 私が何を意味しているかを確認するには、次のようにします。
class A(object):
def __init__(self):
print("Entering A")
super(A, self).__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
A.__init__(self)
B.__init__(self)
print("Leaving C")
すると次のようになります:
Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C
B
init が 2 回呼び出されることに注意してください。次のようにすると:
class A(object):
def __init__(self):
print("Entering A")
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super(C, self).__init__()
print("Leaving C")
すると次のようになります:
Entering C
Entering A
Leaving A
Leaving C
の init は決して呼び出されないことに注意してください。したがって、(および)B
から継承するクラスの init を知ったり制御したりしない限り、記述しているクラス ( ) に対して安全な選択を行うことはできないようです。A
B
C
ベストアンサー1
質問に対する答えは、1 つの非常に重要な側面によって決まります。基本クラスは多重継承用に設計されていますか?
3 つの異なるシナリオがあります。
基本クラスは関連のないスタンドアロン クラスです。
基本クラスが独立して機能できる個別のエンティティであり、お互いを認識していない場合、それらは多重継承用に設計されていません。例:
class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar
重要:も もを呼び出してい
Foo
ないことに注意してください。これが、コードが正しく動作しなかった理由です。Python でのダイヤモンド継承の仕組みにより、をベースクラスとするクラスは を呼び出すべきではありません。お気づきのとおり、これを行うと、ではなく別のクラスの を呼び出すことになるため、多重継承が壊れます。(免責事項:サブクラスでの の使用を避けることは私の個人的な推奨事項であり、Python コミュニティで合意されたコンセンサスではありません。すべてのクラスで を使用することを好む人もいます。常に を記述できると主張しています。Bar
super().__init__()
object
super().__init__()
__init__
object.__init__()
super().__init__()
object
super
アダプタクラスが期待どおりに動作しない場合。object
これは、 を継承し、メソッドを持たないクラスを決して作成してはならないことも意味します。メソッドをまったく__init__
定義しないと、を呼び出すのと同じ効果があります。 クラスが を直接継承する場合は、次のように空のコンストラクタを追加するようにしてください。__init__
super().__init__()
object
class Base(object): def __init__(self): pass
いずれにせよ、この状況では、各親コンストラクターを手動で呼び出す必要があります。これを行うには 2 つの方法があります。
それなし
super
class FooBar(Foo, Bar): def __init__(self, bar='bar'): Foo.__init__(self) # explicit calls without super Bar.__init__(self, bar)
と
super
class FooBar(Foo, Bar): def __init__(self, bar='bar'): super().__init__() # this calls all constructors up to Foo super(Foo, self).__init__(bar) # this calls all constructors after Foo up # to Bar
これら2つの方法にはそれぞれ長所と短所があります。 を使用する場合
super
、クラスは依存性注入一方で、間違いを起こしやすくなります。たとえば、Foo
との順序を変更する場合Bar
( のように)、一致するように呼び出しをclass FooBar(Bar, Foo)
更新する必要があります。 がなければ、この点について心配する必要がなく、コードがはるかに読みやすくなります。super
super
クラスの 1 つは mixin です。
あ混入しますは多重継承で使用するために設計されたクラスです。つまり、ミックスインが自動的に 2 番目のコンストラクターを呼び出すため、両方の親コンストラクターを手動で呼び出す必要はありません。今回は 1 つのコンストラクターのみを呼び出す必要があるため、
super
親クラスの名前をハードコードする必要がないように、 を使用して呼び出すことができます。例:
class FooMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # forwards all unused arguments self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar class FooBar(FooMixin, Bar): def __init__(self, bar='bar'): super().__init__(bar) # a single call is enough to invoke # all parent constructors # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't # recommended because we don't want to hard-code the parent class.
ここで重要な詳細は次のとおりです。
- ミックスインは
super().__init__()
受け取った引数を呼び出して渡します。 - サブクラスはまずミックスインから継承します:
class FooBar(FooMixin, Bar)
。基本クラスの順序が間違っていると、ミックスインのコンストラクターは呼び出されません。
- ミックスインは
すべての基本クラスは協調継承用に設計されています。
協調継承用に設計されたクラスは、ミックスインによく似ています。つまり、未使用の引数をすべて次のクラスに渡します。以前と同様に、呼び出すだけで
super().__init__()
、すべての親コンストラクターが連鎖的に呼び出されます。例:
class CoopFoo: def __init__(self, **kwargs): super().__init__(**kwargs) # forwards all unused arguments self.foo = 'foo' class CoopBar: def __init__(self, bar, **kwargs): super().__init__(**kwargs) # forwards all unused arguments self.bar = bar class CoopFooBar(CoopFoo, CoopBar): def __init__(self, bar='bar'): super().__init__(bar=bar) # pass all arguments on as keyword # arguments to avoid problems with # positional arguments and the order # of the parent classes
この場合、親クラスの順序は重要ではありません。
CoopBar
最初に継承しても、コードは同じように動作します。ただし、これはすべての引数がキーワード引数として渡される場合にのみ当てはまります。位置引数を使用すると、引数の順序が間違ってしまう可能性が高くなるため、協調クラスではキーワード引数のみを受け入れるのが一般的です。これは、前に述べたルールの例外でもあります。 と
CoopFoo
は両方ともCoopBar
を継承しますobject
が、 を呼び出しますsuper().__init__()
。 そうでなければ、協調継承は行われません。
結論: 正しい実装は、継承元のクラスによって異なります。
コンストラクターは、クラスのパブリック インターフェイスの一部です。クラスがミックスインとして、または協調的な継承用に設計されている場合は、そのことを文書化する必要があります。ドキュメントにそのようなことが何も記載されていない場合は、クラスが協調的な多重継承用に設計されていないと想定しても問題ありません。