多重継承のシナリオがあるとしましょう:
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
の__init__
を記述するための2つの典型的なアプローチがあります。
ParentClass.__init__(self)
super(DerivedClass, self).__init__()
ただし、どちらの場合でも、親クラス(A
およびB
) 同じ規則に従わない場合、コードは正しく機能しません (一部が欠落する場合があります) 、または複数回呼び出されます)。
では、正しい方法は何ですか? 「一貫性を保って、どちらか一方に従うだけ」と言うのは簡単ですが、A
またはB
がサードパーティのライブラリからのものである場合はどうなりますか?すべての親クラスのコンストラクターが(そして正しい順序で、一度だけ)呼び出されることを保証できるアプローチはありますか?
編集:私が意味することを見るために、私がそうするなら:
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
B
のinitは呼び出されないことに注意してください。そのため、継承するクラスの初期化を知らない/制御しない限り(A
およびB
)私が書いているクラス(C
)に対して安全な選択をすることはできないようです。
あなたの質問に対する答えは、1つの非常に重要な側面に依存します:あなたのベースクラスは多重継承のために設計されていますか?
3つの異なるシナリオがあります。
基本クラスが独立して機能できる個別のエンティティであり、互いに認識していない場合、それらは多重継承用に設計されていますnot。例:
class Foo:
def __init__(self):
self.foo = 'foo'
class Bar:
def __init__(self, bar):
self.bar = bar
重要:Foo
もBar
もsuper().__init__()
!を呼び出さないことに注意してください。これが、コードが正しく機能しなかった理由です。 Pythonでのダイヤモンドの継承の仕組みにより、基本クラスがobject
であるクラスは、super().__init__()
を呼び出してはなりません。お気づきのとおり、そうすると、object.__init__()
ではなく別のクラスの__init__
を呼び出すことになり、多重継承が壊れます。 (免責事項:object
- subclassesでsuper().__init__()
を回避することは私の個人的な推奨事項であり、決して合意された合意ではありませんpythonコミュニティ。一部の人々はすべてのクラスでsuper
を使用することを好み、クラスが期待どおりに動作しない場合は常に adapter と書くことができると主張します。)
これはまた、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
を使用する場合、クラスは dependency injection をサポートします。一方、間違いを犯しやすい。たとえば、Foo
とBar
の順序を変更する場合(class FooBar(Bar, Foo)
など)、super
呼び出しを一致するように更新する必要があります。 super
がなければ、これについて心配する必要はなく、コードははるかに読みやすくなります。
mixin は、多重継承で使用されるdesignedのクラスです。これは、ミックスインが自動的に2番目のコンストラクターを呼び出すため、両方の親コンストラクターを手動で呼び出す必要がないことを意味します。今回は単一のコンストラクターを呼び出すだけなので、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)
。基本クラスの順序が間違っている場合、mixinのコンストラクターは呼び出されません。協調的継承のために設計されたクラスは、ミックスインによく似ています。すべての未使用の引数を次のクラスに渡します。前と同じように、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__()
を呼び出します。そうしなかった場合、協力的な継承はありません。
結論:正しい実装は、継承元のクラスによって異なります。
コンストラクターは、クラスのパブリックインターフェイスの一部です。クラスがミックスインとして、または協調的継承のために設計されている場合、それを文書化する必要があります。ドキュメントでこの種のことについて何も言及されていない場合、クラスは協調多重継承用に設計されていないと想定しても安全です。
この記事は、協調的な多重継承の説明に役立ちます。
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
メソッドの解決順序を示す便利なメソッドmro()
に言及しています。 2番目の例では、super
でA
を呼び出しますが、super
呼び出しはMROで続行されます。順序の次のクラスはB
です。これがB
のinitが初めて呼び出される理由です。
公式pythonサイトの技術的な記事は次のとおりです。
どちらのアプローチ(「新しいスタイル」または「古いスタイル」)も機能しますA
およびB
のソースコードを制御できる場合。そうでない場合は、アダプタークラスの使用が必要になる場合があります。
class A(object):
def __init__(self):
print("-> A")
super(A, self).__init__()
print("<- A")
class B(object):
def __init__(self):
print("-> B")
super(B, self).__init__()
print("<- B")
class C(A, B):
def __init__(self):
print("-> C")
# Use super here, instead of explicit calls to __init__
super(C, self).__init__()
print("<- C")
>>> C()
-> C
-> A
-> B
<- B
<- A
<- C
ここで、メソッド解決順序(MRO)は次を指示します。
C(A, B)
は最初にA
を指定し、次にB
を指定します。 MROはC -> A -> B -> object
です。super(A, self).__init__()
は、C.__init__
からB.__init__
で開始されたMROチェーンに沿って続きます。super(B, self).__init__()
は、C.__init__
からobject.__init__
で開始されたMROチェーンに沿って続きます。このケースは、多重継承用に設計されていると言えます。
class A(object):
def __init__(self):
print("-> A")
print("<- A")
class B(object):
def __init__(self):
print("-> B")
# Don't use super here.
print("<- B")
class C(A, B):
def __init__(self):
print("-> C")
A.__init__(self)
B.__init__(self)
print("<- C")
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C
ここでは、A.__init__
とB.__init__
が明示的に呼び出されるため、MROは重要ではありません。 class C(B, A):
も同様に機能します。
このケースは、以前のように新しいスタイルの多重継承用に「設計」されていませんが、多重継承は依然として可能です。
ここで、A
とB
がサードパーティライブラリのものである場合、つまりA
とB
のソースコードを制御できない場合簡単な答え:必要なsuper
呼び出しを実装するアダプタークラスを設計し、空のクラスを使用してMROを定義する必要があります( Raymond Hettingerのsuper
に関する記事 -特にセクション「組み込み方法非協力的なクラス」)。
A
はsuper
を実装しません。 B
はclass A(object):
def __init__(self):
print("-> A")
print("<- A")
class B(object):
def __init__(self):
print("-> B")
super(B, self).__init__()
print("<- B")
class Adapter(object):
def __init__(self):
print("-> C")
A.__init__(self)
super(Adapter, self).__init__()
print("<- C")
class C(Adapter, B):
pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C
クラスAdapter
は、super
を実装して、C
がMROを定義できるようにします。MROは、super(Adapter, self).__init__()
の実行時に機能します。
そして、それが逆の場合はどうなりますか?
A
はsuper
を実装します。 B
はclass A(object):
def __init__(self):
print("-> A")
super(A, self).__init__()
print("<- A")
class B(object):
def __init__(self):
print("-> B")
print("<- B")
class Adapter(object):
def __init__(self):
print("-> C")
super(Adapter, self).__init__()
B.__init__(self)
print("<- C")
class C(Adapter, A):
pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C
Adapter.__init__
で実行の順序が切り替えられることを除いて、ここで同じパターン。 super
を最初に呼び出し、次に明示的な呼び出しを行います。サードパーティの親を持つ各ケースには、一意のアダプタークラスが必要であることに注意してください。
したがって、継承するクラスの初期化(
A
およびB
)を知らない/制御しない限り、作成中のクラス(C
)に対して安全な選択をすることはできません。
アダプタクラスを使用して、A
およびB
のソースコードを control しないケースを処理できますが、 know親クラスのinitがsuper
を実装する方法(もしあれば)。
サードパーティライブラリのサブクラスクラスを乗算する場合、いいえ、ベースクラスのプログラミング方法に関係なく実際に動作するベースクラス__init__
メソッド(または他のメソッド)を呼び出すブラインドアプローチはありません。
super
は、クラス作成者に知られる必要のない複雑な多重継承ツリーの一部としてメソッドを協調的に実装するように設計されたクラスを書くことを可能にします。ただし、super
を使用する場合と使用しない場合がある任意のクラスから正しく継承するために使用する方法はありません。
基本的に、クラスがsuper
を使用して、または基本クラスへの直接呼び出しを使用してサブクラス化されるように設計されているかどうかは、クラスの「パブリックインターフェイス」の一部であるプロパティであり、そのように文書化する必要があります。ライブラリの作成者が期待する方法でサードパーティのライブラリを使用しており、ライブラリに適切なドキュメントがある場合、通常、特定のことをサブクラス化するために必要なことを通知します。そうでない場合は、サブクラス化するクラスのソースコードを調べ、その基本クラス呼び出し規則を確認する必要があります。ライブラリ作成者did n'tが期待する方法で1つ以上のサードパーティライブラリの複数のクラスを組み合わせている場合、スーパークラスメソッドを一貫して呼び出すことができない場合がありますまったく;クラスAがsuper
を使用する階層の一部であり、クラスBがスーパーを使用しない階層の一部である場合、どちらのオプションも機能することは保証されません。特定のケースごとに機能する戦略を理解する必要があります。
レイモンドが答えで言ったように、A.__init__
とB.__init__
への直接呼び出しは問題なく機能し、コードは読みやすくなります。
ただし、C
とそれらのクラスの間の継承リンクは使用しません。そのリンクを活用すると、一貫性が高まり、最終的なリファクタリングが容易になり、エラーが発生しにくくなります。その方法の例:
class C(A, B):
def __init__(self):
print("entering c")
for base_class in C.__bases__: # (A, B)
base_class.__init__(self)
print("leaving c")