Pythonのsuper()は多重継承でどのように機能しますか?


888

私は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)と関係があることを知っています。


実際、多重継承は、が役立つ唯一のケースsuper()です。無駄なオーバーヘッドである線形継承を使用するクラスで使用することはお勧めしません。
バクサウ2018

9
@Bachsauは技術的には正しいオーバーヘッドですが、オーバーヘッドは小さいですが、super()はよりPython的であり、時間の経過とともにコードのリファクタリングと変更が可能です。名前付きクラス固有のメソッドが本当に必要でない限り、super()を使用してください。
ポールウィップ

2
のもう1つの問題super()は、すべてのサブクラスにも強制的にそれを使用させることですが、を使用しない場合はsuper()、サブクラスを作成するすべての人が自分で決めることができます。それを使用している開発者super()がそれが使用されたことを知らない、または知らない場合、追跡するのが非常に難しいmroの問題が発生する可能性があります。
Bachsau

私は事実上、ここでそれぞれの答えが何らかの方法で混乱していることを発見しました。代わりに、ここで実際に参照します
matanster

回答:


709

これは、Guido自身のブログ投稿のMethod Resolution Order(以前の2つの試みを含む)で妥当な量の詳細とともに説明されています。

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

class Third(First, Second):
    ...

Pythonはまずを調べます。属性がないFirst場合Firstはを調べますSecond

この状況は、継承がパスの交差を開始すると(たとえば、Firstから継承された場合Second)、さらに複雑になります。詳細については、上記のリンクを参照してください。ただし、簡単に言うと、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がどのように構築されているかを示すことです。「最初の\ n2番目の\ 3番目」などを印刷すること意図されていません。もちろん、できます。もちろん、例をsuper()いじって、呼び出しを追加し、何が起こるかを確認して、Pythonの継承モデルをより深く理解してください。しかし、ここでの私の目標は、それをシンプルに保ち、MROがどのように構築されるかを示すことです。そしてそれは私が説明したように構築されています:

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

12
First、Second、Third [ pastebin.com/ezTyZ5Wa ]でsuper()の呼び出しを開始すると、より興味深い(そしておそらく、さらに混乱する)ようになります。
gatoatigrado 2012

52
最初のクラスにスーパーコールがないことは、この回答の非常に大きな問題だと思います。どのように/なぜそれが質問に対する重要な批判的理解が失われるのか、なぜ議論されずに。
Sam Hartman

3
この答えは間違っています。親のsuper()呼び出しがないと、何も起こりません。@lifelessの答えは正しいです。
Cerin、2015年

8
@Cerinこの例のポイントは、MROの構築方法を示すことです。この例は、「first \ nsecond \ third」などを印刷することを意図したものではありません。そして、MROは確かに正しいです:Fourth .__ mro__ ==(<class ' main .Fourth'>、<class ' main .Second'>、<class ' main .Third'>、<class ' main .First'>、<タイプ 'object'>)
rbp

2
私の知る限り、この答えにはOPの質問の1つが欠けています。それは、「もう1つを実行したい場合はどうすればよいですか?」です。この質問への答えを見たいのですが。基本クラスに明示的に名前を付けるだけですか?
Ray

251

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

これはコードの修正バージョンです:

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()コールは、そうでない場合、実行はの終わりで停止し、第1、第2は、あまりにもそれを持っている必要がある理由各ステップ、でMROの次の方法を見つけましたSecond.__init__()

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

>>> Third()
second
first
third

90
これらのクラスが自分自身を初期化するために異なるパラメーターを必要とする場合はどうしますか?
カーフ州2014年

2
「協調サブクラス化」
Quant Metropolis

6
このようにして、両方の基本クラスのinitメソッドが実行されますが、元の例ではMROで最初に検出されたinitのみが呼び出されます。「協調サブクラス化」という用語はそれを暗示していると思いますが、明確化は有用だったでしょう(「明示的は暗黙的よりも優れている」と思います;))
Quant Metropolis

1
はい。superを介して呼び出されるメソッドに異なるパラメーターを渡す場合、object()に向かってMROを実行するそのメソッドのすべての実装には、互換性のあるシグネチャが必要です。これは、キーワードパラメータを通じて実現できます。メソッドが使用するよりも多くのパラメータを受け入れ、余分なパラメータは無視します。これを行うのは一般に醜いと考えられており、ほとんどの場合、新しいメソッドを追加する方が優れていますが、initは(ほぼ?)特別なメソッド名として一意ですが、ユーザー定義のパラメーターを持っています。
気のない

15
多重継承の設計はPythonでは本当に悪いです。基底クラスはほとんどそれを引き出すために起こっているかを知る必要がある、とどのように他の多くの基底クラス派生は派生し、どのような順序で...そうだろう superどちらか(理由は、パラメータの不一致の)実行に失敗する、またはそれがコールすることはありませんいくつかのベース(superリンクを壊すベースの1つを記述しなかったため)!
Nawaz

186

私は手の込んだしたい死んで答えを私は(スーパーの使用方法について読み始めたときので、私はすぐにそれを得るdid't、Pythonで複数の継承階層に)少し。

理解する必要があるのsuper(MyClass, self).__init__()、完全な継承階層のコンテキストで使用されるメソッド解決順序付け(MRO)アルゴリズムに従って次の __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以降で改良されて複雑なケースでもうまく機能するようになりましたが、「深さ優先の左から右へのトラバーサル」+「最後の重複を削除する」の使用は、ほとんどの場合まだ機能します(ください。そうでない場合はコメント)。グイドによるブログ投稿を必ず読んでください!


6
インサイドは:なぜ私はまだ理解していないinitはまずスーパー(まず、自己)の.__のinit __()を呼び出すのinitそれがどのようなMROのおもむくままであるため、第二のを!
user389955 2017

@ user389955作成されたオブジェクトは、すべてのinitメソッドを持つThirdタイプです。したがって、MROがすべての初期化関数のリストを特定の順序で作成すると仮定すると、すべてのスーパーコールで、最後に到達するまで1歩進んでいます。
Sreekumar R

15
ステップ3にはさらに説明が必要だと思います。Thirdから継承しなかった場合はSecondsuper(First, self).__init__が呼び出さobject.__init__れ、戻った後に「first」が出力されます。しかしので、Third両方からの継承FirstSecond、むしろ呼び出しよりもobject.__init__First.__init__にのみ、最終的な呼び出しがMROのおもむくままobject.__init__保存され、印刷文のFirstとはSecondまで到達していないobject.__init__リターン。以来Second呼び出すことが最後だったobject.__init__、それは内部を返すSecondに戻す前にFirst
MountainDrew

1
興味深いことに、PyCharmが知っているようだ、このすべて(それが認識するようにそのヒントは、パラメータはまた、入力の共分散のいくつかの概念を持っているスーパーへの呼び出し。一緒に行くかについて話しList[subclass]List[superclass]いる場合subclassのサブクラスがあるsuperclassListから来typingPEP 483のモジュールiirc)
。– Reb.Cabin

いい投稿ですが、コンストラクタの引数に関する情報が欠けています。つまり、SecondとFirstが異なる引数を期待するとどうなりますか?Firstのコンストラクターは、いくつかの引数を処理し、残りをSecondに渡す必要があります。そうですか?FirstがSecondに必要な引数について知る必要があるのは、私には正しく聞こえません。
クリスチャンK.

58

これは「ダイヤモンドの問題」と呼ばれ、ページにはPythonに関するエントリがありますが、簡単に言うと、Pythonはスーパークラスのメソッドを左から右に呼び出します。


これはダイヤモンドの問題ではありません。ダイヤモンド問題には4つのクラスが含まれ、OPの質問には3つのクラスのみが含まれます。
Ian Goodfellow 2011

147
object4番目です
GP89、2011年

28

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

これAは拡張可能な基本クラスでBありC、MixInクラスはどちらも機能を提供しますfAそして、Bどちらもパラメータvを期待します__init__C期待していwます。関数fは1つのパラメータを取りますyQ3つのクラスすべてから継承します。MixInF以下のためのミックスイン・インターフェースであるBC


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)

MROは、継承を使用する関数間でさまざまな引数を処理することなく、十分に大きなトピックであるため、これはおそらく別の質問と回答になるはずです(多重継承はその特別なケースです)。
2014

8
理論的にはそうです。実際には、PythonでDiamondの継承に遭遇するたびにこのシナリオが出てきたので、ここに追加しました。なぜなら、私はダイヤモンドの継承をきちんと避けることができないたびに行くところだからです。:ここでは、将来の私のためにいくつかの余分なリンクですrhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/...
brent.payneは

私たちが欲しいのは、意味的に意味のあるパラメータ名を持つプログラムです。ただし、この例では、ほとんどすべてのパラメーターに匿名の名前が付けられているため、元のプログラマーがコードを文書化したり、別のプログラマーがコードを読み取ったりすることがはるかに困難になります。
Arthur

説明的な名前を付けてgithubリポジトリへのプルリクエストをいただければ
幸いです

@ brent.payne @Arthurは、あなたのアプローチ全体が名前付きパラメーターではなくargs/ を使用することに依存していることを意味すると思いkwargsます。
2017年

25

これは直接回答しないことを理解しています 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__()と呼ばれることはありません。


5
継承された各クラスを呼び出さなかったため、呼び出されません。問題はむしろ場合ということであるFirstSecondの両方の別のクラスを継承して、(ダイヤモンドの出発点)、この一般的なクラスが2回呼び出され、それを直接呼んでいます。スーパーはこれを回避しています。
Trilarion 2014

@Trilarionええ、そうはならないと確信していました。しかし、私は決定的に知りませんでしたし、可能性は非常に低いとしても、私がそうするように述べたくありませんでした。これは、object2回呼び出されることの良い点です。私はそれについて考えていませんでした。親クラスを直接呼び出すようにしたかっただけです。
Seaux 2014

残念ながら、これはinitがプライベートメソッドにアクセスしようとすると
失敗し

21

全体

すべてが子孫であると仮定するとobject(そうでない場合は自分で行います)、Pythonはクラス継承ツリーに基づいてメソッド解決順序(MRO)を計算します。MROは3つのプロパティを満たします。

  • クラスの子供たちは両親の前に来る
  • 左の親が右の親の前に来る
  • クラスはMROに一度だけ表示されます

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

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

  1. 正しい

メソッドが呼び出されると、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

私は、あなたがアクセスできることを確認しLeft使用してsuper()からChildRight内部からアクセスしたいとしますChild。super RightChild使用してアクセスする方法はありますか?またはRight、内部から直接呼び出す必要がありsuperますか?
alpha_989

4
@ alpha_989特定のクラスのメソッドのみにアクセスする場合は、superを使用するのではなく、そのクラスを直接参照する必要があります。スーパーは、特定のクラスのメソッドに到達するのではなく、継承の連鎖をたどることです。
ザグス2018年

1
「クラスはMROに一度だけ現れる」と明示的に言及していただきありがとうございます。これは私の問題を解決しました。これで、多重継承がどのように機能するかがようやくわかりました。誰かがMROの特性に言及する必要がありました!
Tushar Vazirani

18

@calfzhouのコメントについては、通常どおり使用できます**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

まだカバーされていないもう1つのポイントは、クラスの初期化用のパラメーターを渡すことです。の宛先は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が複数回呼び出される場合は失敗します。

結論として:協調継承と初期化用のスーパーおよび特定のパラメーターがうまく連携していません。


5
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 = [First、Second]。ここで、Firstで定義されたsuper in initを呼び出すと、MROの検索が続行され、Secondで定義されたinitが見つかります。superを呼び出すと、デフォルトのオブジェクトinitがヒットします。この例がコンセプトを明確にすることを願っています。

Firstからsuperを呼び出さない場合。チェーンが停止し、次の出力が表示されます。

first 10
that's it

1
これは、クラスで最初に「print」を最初に呼び出し、次に「super」を呼び出したためです。
ロッキーチー

2
それは
召命

4

pythonthehardwayの学習では、誤解しない限り、組み込み関数super()と呼ばれるものを学びます。super()関数を呼び出すと、継承が親と「兄弟」を通過するのに役立ち、より明確に見えるようになります。私はまだ初心者ですが、python2.7でこのsuper()を使用した経験を共有したいです。

このページのコメントを読み終えると、メソッド解決順序(MRO)が聞こえます。メソッドは記述した関数であり、MROは深さ優先左から右へのスキームを使用して検索および実行します。あなたはそれについてより多くの研究を行うことができます。

super()関数を追加する

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

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がコーディングを実行する方法をより明確に確認し、継承が意図したメソッドにアクセスできることを確認できます。


詳細なデモをありがとう!
Tushar Vazirani

3

@Visionscaperの上部に追加します。

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

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

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

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

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

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"

「むしろ、Secondは階層サブセットの先頭の位置に表示され、末尾の位置には表示されないためです。」先頭または末尾の位置が何であるか、階層サブセットが何であるか、またはどのサブセットを参照しているかは明確ではありません。
OrangeSherbet 2017

末尾の位置とは、クラス階層の上位にあるクラスを指し、その逆も同様です。基本クラス「オブジェクト」は、尾の終わりにあります。mroアルゴリズムを理解するための鍵は、「2番目」が「最初」のスーパーとしてどのように表示されるかです。通常は「オブジェクト」クラスであると想定します。それは真実ですが、「ファースト」クラスの観点からのみです。ただし、「3番目」クラスの観点から見ると、「最初」の階層順序は異なり、上記のように計算されます。mroアルゴリズムは、複数の継承されたすべてのクラスに対してこのパースペクティブ(または階層サブセット)を作成しようとします
supi

3

Python 3.5以降では、継承は予測可能であり、私にとって非常にいいように見えます。このコードを見てください:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


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


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

出力:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

ご覧のとおり、継承されたチェーンと同じ順序で、継承されたチェーンごとにfooを正確に1回呼び出します。を呼び出すことでその順序を取得できますmro

4番目-> 3番目->最初-> 2番目->ベース->オブジェクト


2

追加できるものがまだあるかもしれません。Djangorest_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)

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

具体的なクラスの継承リストに、LoginToPostbefore ListCreateAPIViewLoginToPutOrDeletebeforeを追加しRetrieveUpdateDestroyAPIViewます。私の具体的なクラスgetは装飾されません。


1

私の将来の参考のためにこの回答を投稿します。

Python Multiple Inheritanceはダイヤモンドモデルを使用する必要があり、関数のシグネチャはモデル内で変更されません。

    A
   / \
  B   C
   \ /
    D

サンプルコードスニペットは;-

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

ここでクラスAは object


1
A呼び出す必要があります__init__Aはメソッドを「発明」しなかった__init__ので、他のクラスがAMROの初期に持っているとは想定できません。__init__メソッドが呼び出さない(および呼び出すべきではない)唯一のクラスsuper().__init__objectです。
chepner

うん。objectclass A (object) :
ですから

Aobjectパラメータを追加する場合はできません__init__
chepner
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.