web-dev-qa-db-ja.com

Pythonのsuper()は多重継承とどのように連携しますか?

私はPythonのオブジェクト指向プログラミングについてはかなり新しいので、特に多重継承に関してはsuper()関数(新しいスタイルクラス)を理解するのが困難です。

たとえば、次のようなものがあるとします。

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Third()クラスは両方のコンストラクタメソッドを継承するのでしょうか。そうであれば、どれがsuper()で実行されるのでしょうか、そしてそれはなぜですか?

そして、あなたが他のものを走らせたいならば、どうですか?私はそれがPythonのメソッド解決の順序( _ mro _ )と関係があることを知っています。

733
Callisto

これは、Guido自身のブログ投稿 Method Resolution Order (以前の2回の試行を含む)で、妥当な範囲の詳細で詳しく説明されています。

あなたの例では、Third()First.__init__を呼び出します。 Pythonは、クラスの親の各属性を左から右の順にリストしながら探します。この場合、私たちは__init__を探しています。だから、あなたが定義すれば

class Third(First, Second):
    ...

PythonはFirstを見ることから始めます、そしてFirstがその属性を持っていなければ、それはSecondを見るでしょう。

継承がパスを横切って開始するとき(例えばFirstSecondから継承される場合)、この状況はより複雑になります。詳細については上記のリンクをお読みください。ただし、簡単に言うと、Pythonは各クラスが継承リストに表示される順序を子クラス自体から順に維持しようとします。

だから、たとえば、あなたが持っていた場合:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

mROは[Fourth, Second, Third, First].になります

ところで、Pythonが首尾一貫したメソッド解決の順序を見つけられない場合、ユーザーを驚かせるかもしれない振る舞いに戻るのではなく、例外を発生させるでしょう。

あいまいなMROの例を追加するように編集しました。

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

ThirdのMROは[First, Second]または[Second, First]のどちらであるべきですか?明白な期待はありません、そしてPythonはエラーを発生させるでしょう:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

編集: 上記の例にはsuper()呼び出しがないと主張する人が何人かいるので説明しておきましょう。例の要点はMROがどのように構成されているかを示すことです。それらは ではありません "first\nsecond\third"または何でも印刷することを意図しています。もちろん、super()呼び出しを追加し、何が起こるのかを見て、Pythonの継承モデルをより深く理解することができます。しかし、ここでの私の目標は、それを単純にして、MROがどのように構築されているのかを示すことです。そしてそれは私が説明したように作られています:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
605
rbp

あなたのコードと他の答えはすべてバグがあります。協調的サブクラス化が機能するために必要な最初の2つのクラスには、super()呼び出しがありません。

これがコードの修正版です。

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

super()呼び出しは各ステップでMRO内の次のメソッドを見つけます、それがFirstとSecondもそれを持っていなければならない理由です、そうでなければ実行はSecond.__init__()の終わりで停止します。

これは私が得るものです:

>>> Third()
second
first
third
215
lifeless

Pythonの多重継承階層でsuper()を使用する方法を読み始めたとき、すぐには得られなかったため、 lifelessによる答え を詳しく説明したかったのです。

理解する必要があるのは、super(MyClass, self).__init__()が、使用されるMethod Resolution Ordering(MRO)アルゴリズムに従ってnext__init__メソッドを提供することです完全な継承階層のコンテキストで

この最後の部分を理解することは非常に重要です。例をもう一度考えてみましょう。

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

メソッド解決順序に関するこの記事によると Guido van Rossumにより、__init__を解決する順序が(Python 2.3より前に)計算されます。右へのトラバース」:

Third --> First --> object --> Second --> object

最後のものを除くすべての重複を削除すると、次のようになります。

Third --> First --> Second --> object

したがって、Thirdクラスのインスタンスをインスタンス化するとどうなるかを追跡しましょう。 x = Third()

  1. MROによるとThird.__init__が実行されます。
    • Third(): enteringを出力します
    • その後、super(Third, self).__init__()が実行され、MROは呼び出されたFirst.__init__を返します。
  2. First.__init__が実行されます。
    • First(): enteringを出力します
    • その後、super(First, self).__init__()が実行され、MROは呼び出されたSecond.__init__を返します。
  3. Second.__init__が実行されます。
    • Second(): enteringを出力します
    • その後、super(Second, self).__init__()が実行され、MROは呼び出されたobject.__init__を返します。
  4. object.__init__が実行されます(コード内にprintステートメントはありません)
  5. 実行はSecond.__init__に戻り、Second(): exitingを出力します
  6. 実行はFirst.__init__に戻り、First(): exitingを出力します
  7. 実行はThird.__init__に戻り、Third(): exitingを出力します

これにより、Third()のインスタンス化の結果が次のようになります。

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

MROアルゴリズムはPython 2.3以降から改善されて、複雑なケースでうまく機能しますが、「深さ優先左から右へのトラバーサル」+「最後に期待される重複の除去」を使用すると思いますほとんどの場合に機能します(そうでない場合はコメントしてください)。 Guidoのブログ投稿を必ず読んでください!

162
Visionscaper

これは Diamond Problem として知られています。このページにはPythonのエントリがありますが、要するにPythonはスーパークラスのメソッドを左から右に呼び出します。

48
monoceres

これは、初期化のために異なる変数を持つ複数の継承を持ち、同じ関数呼び出しを持つ複数のMixInsを持つという問題を解決した方法です。渡された** kwargsに変数を明示的に追加し、スーパーコールのエンドポイントになるMixInインターフェイスを追加する必要がありました。

ここでAは拡張可能な基本クラスで、BCはどちらも関数fを提供するMixInクラスです。 ABはどちらも、それぞれの__init__にパラメーターvを必要とし、Cwを必要とします。関数fは1つのパラメータyを取ります。 Qは3つすべてのクラスから継承します。 MixInFBCのミックスインインターフェースです。


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)
25
brent.payne

私はこれがsuper()の質問に直接答えないことを理解しています、しかし私はそれが共有するのに十分に関連があると思います。

継承した各クラスを直接呼び出す方法もあります。


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

First__init__()が呼び出されないと確信しているので、このようにすると、それぞれ手動で呼び出す必要があります。

18
Seaux

全体

すべてがobjectから派生していると仮定して(そうでない場合はあなたがあなた自身のものです)、Pythonはクラス継承ツリーに基づいてメソッド解決順序(MRO)を計算します。 MROは3つの特性を満たします。

  • クラスの子供たちは両親の前に来る
  • 左の両親は右の両親の前に来る
  • クラスはMROに一度だけ現れる

そのような順序付けが存在しない場合、Pythonはエラーを起こします。これの内部の働きはクラスの先祖のC3 Linerizationです。それについてのすべてをここに読んでください: https://www.python.org/download/releases/2.3/mro/

したがって、以下の両方の例では、次のようになります。

メソッドが呼び出されると、MRO内でそのメソッドが最初に現れるのは、呼び出されるものです。そのメソッドを実装していないクラスはすべてスキップされます。そのメソッド内でsuperを呼び出すと、MRO内でそのメソッドが次に出現する場所が呼び出されます。したがって、クラスを継承する順序とメソッド内のsuperの呼び出し位置の両方が重要になります。

各メソッドの先頭にsuperを付ける

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child()出力:

parent
right
left
child

各メソッドの最後にsuperがある

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child()出力:

child
left
right
parent
16
Zags

@ calfzhou's comment について、通常どおり**kwargsを使用できます。

オンライン実行例

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

結果:

A None
B hello
A1 6
B1 5

それらを位置的に使用することもできます。

B1(5, 6, b="hello", a=None)

しかし、あなたはMROを覚えていなければなりません、それは本当に混乱します。

ちょっと面倒かもしれませんが、メソッドをオーバーライドするときに*args**kwargsを使うのを忘れてしまう人たちがいることに気づきました。

15
Marco Sulla

まだカバーされていない別のポイントはクラスの初期化のためのパラメータを渡すことです。 superの行き先はサブクラスに依存するので、パラメータを渡す唯一の良い方法はそれらをまとめてパックすることです。それから、異なる意味を持つ同じパラメータ名を持たないように注意してください。

例:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

を与えます:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

スーパークラス__init__を直接呼び出してパラメータを直接指定するのは魅力的ですが、スーパークラス内でsuper呼び出しがある場合やMROが変更され、実装によってはクラスAが複数回呼び出される場合は失敗します。

結論としては、協調継承と初期化のためのスーパーおよび特定のパラメータは、うまく協調していません。

11
Trilarion
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

出力は

first 10
second 20
that's it

Third()を呼び出すと、Thirdで定義されている init が検索されます。そして、そのルーチン内でsuperを呼び出すと、Firstで定義されている init が呼び出されます。 MRO = [1、2]。これで、Firstで定義されているsuper in init はMROの検索を続け、Secondで定義されているfind init を呼び出し、デフォルトのobject init を呼び出すことになります。この例が概念を明確にすることを願っています。

あなたがファーストからスーパーを呼ばないならば。チェーンが停止し、次のような出力が得られます。

first 10
that's it
4
Seraj Ahmad

私は に加えたいのですが - @Visionscaperが言うこと

Third --> First --> object --> Second --> object

この場合、インタプリタは、オブジェクトクラスが重複しているので除外するのではなく、Secondが階層サブセットの先頭位置に表示され、末尾位置に表示されないためです。オブジェクトは末尾位置にのみ現れるため、優先順位を決定するためのC3アルゴリズムでの強い位置とは見なされません。

クラスCの線形化(mro)L(C)は、

  • クラスC
  • プラスのマージ
    • その親の線形化P1、P2、.. = L(P1、P2、...)そして
    • その親のリストP1、P2、..

Linearised Mergeは、順序が重要であるため、末尾ではなくリストの先頭として表示される共通のクラスを選択することによって行われます(以下で明らかになります)。

Thirdの線形化は、次のように計算できます。

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

したがって、次のコードのsuper()実装の場合:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

このメソッドがどのように解決されるかは明らかになります

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"
2
supi

誤解しない限り、学ぶために私はsuper()と呼ばれるものを組み込み関数として学んでいます。 super()関数を呼び出すことで、継承が親と '兄弟'を通過するのを助け、より明確に見えるようにすることができます。私はまだ初心者ですが、python2.7でこのsuper()を使用した経験を共有したいと思います。

このページのコメントを読み終えたら、Method Resolution Order(MRO)というメソッドがあなたが書いた関数であることが聞こえます。MROは、深さ - 左 - 右 - 右スキームを使用して検索と実行を行います。あなたはそれについてもっと研究をすることができます。

Super()関数を追加することによって

super(First, self).__init__() #example for class First.

複数のインスタンスと 'family'をsuper()でつなぐことができます。それらのそれぞれとすべての人を追加することによってです。そして、それはメソッドを実行し、それらを通過し、あなたが見逃していないことを確認します!しかし、前後にそれらを追加することはあなたが学習pythonthehardway演習44を終えたかどうかあなたが知っている違いを生むでしょう。楽しみを始めましょう!

下記の例では、コピー&ペーストして実行してみてください。

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

どのように動作しますか? five()のインスタンスは次のようになります。各ステップは、スーパー関数が追加されたクラスからクラスへと進みます。

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

親が見つかり、それは3番目と4番目に続きます!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

これで、super()を持つすべてのクラスがアクセスされました。親クラスが見つかって実行されたので、今度は継承内の関数のボックスを解除してコードを完成させます。

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

上記のプログラムの結果:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

私にとってはsuper()を追加することで、Pythonがどのようにコーディングを実行するのかをより明確にし、継承が意図したメソッドにアクセスできることを確認することができます。

1
Will MeetYou

まだ追加できるものがあるかもしれません。Django rest_frameworkとデコレーターを使用した小さな例です。これは、暗黙の質問に対する答えを提供します:「とにかくこれが欲しいのですか?」

前述のように、Django rest_frameworkを使用しており、汎用ビューを使用しています。データベース内のオブジェクトのタイプごとに、GETおよびPOSTを提供する1つのビュークラスがあります。オブジェクトのリスト、および個々のオブジェクトのGET、PUT、DELETEを提供する他のビュークラス用。

次に、Djangoのlogin_requiredで装飾したいPOST、PUT、およびDELETEです。これが両方のクラスに影響を与えることに注意してください。ただし、いずれかのクラスのすべてのメソッドではありません。

ソリューションは複数の継承を経ることができます。

from Django.utils.decorators import method_decorator
from Django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

他の方法についても同様です。

具象クラスの継承リストでは、LoginToPostの前にListCreateAPIViewを、LoginToPutOrDeleteの前にRetrieveUpdateDestroyAPIViewを追加します。私の具体的なクラスのgetは装飾されていません。

1
mariotomo