web-dev-qa-db-ja.com

Pythonの「スーパー」はどのように正しく機能しますか?

私はPython 2.5を実行しているので、この質問はPython 3.に当てはまらない場合があります。3。多重継承を使用してダイヤモンドクラス階層を作成し、最も派生したクラスPythonは正しいこと(TM)を実行します。これは、最も派生したクラスのコンストラクタ、次に左から右にリストされている親クラス、そして祖父母のコンストラクタを呼び出します。 Pythonの [〜#〜] mro [〜#〜] はよく知っていますが、これは私の質問ではありません。superから返されたオブジェクトが実際にsuperの呼び出しと通信する方法に興味があります親クラスは正しい順序です。次のコード例を検討してください:

_#!/usr/bin/python

class A(object):
    def __init__(self): print "A init"

class B(A):
    def __init__(self):
        print "B init"
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        super(D, self).__init__()

x = D()
_

コードは直感的なことを行い、出力します:

_D init
B init
C init
A init
_

ただし、Bのinit関数でsuperの呼び出しをコメント化すると、AもCのinit関数も呼び出されません。これは、Bがsuperを呼び出したときに、クラス階層全体でのCの存在を何らかの形で認識していることを意味します。 superはオーバーロードされたget演算子でプロキシオブジェクトを返すことを知っていますが、Dのinit定義でsuperによって返されたオブジェクトは、Bのinit定義でsuperによって返されたオブジェクトにCの存在をどのように伝えますか?スーパーユースの後続の呼び出しで保存された情報は、オブジェクト自体に保存されていますか?もしそうなら、なぜセルフではなくスーパーではないのですか?

編集:Jekkeは、superはクラスのインスタンスではなくクラスの属性であるため、self.superではないことを正しく指摘しました。概念的にはこれは理にかなっていますが、実際にはスーパーもクラスの属性ではありません!これをインタープリターでテストするには、BがAから継承する2つのクラスAとBを作成し、dir(B)を呼び出します。 superまたは___super___属性はありません。

51
Joseph Garvin

以下に、私がこれまでに望んでいたよりも詳細かつ正確にあなたの質問に答えるリンクをいくつか提供しました。ただし、時間を節約するために、私もあなたの質問に自分の言葉で答えます。私はそれをポイントに入れます-

  1. superは組み込み関数であり、属性ではありません。
  2. Pythonのすべてのtype(class)には、特定のインスタンスのメソッド解決順序を格納する___mro___属性があります。
  3. Superの各呼び出しは、super(type [、object-or-type])という形式です。ここでは、2番目の属性がオブジェクトであると仮定します。
  4. スーパーコールの開始時点では、オブジェクトは派生クラスのタイプです(たとえばDC)。
  5. superは、MROのクラスで、最初の引数として指定されたクラスの後に(この場合はDCの後のクラス)、一致するメソッドを検索します(この場合は___init___)。
  6. 一致するメソッドが見つかると(クラスBC1など)、それが呼び出されます。
    (このメソッドはスーパーを使用する必要があるので、使用すると想定しています-Pythonのスーパーは気の利いているが使用できない-下記のリンクを参照)そのメソッドは、オブジェクトのクラスのMROで次のメソッドを検索します、BC1の右側.
  7. すべてのメソッドが見つかり、呼び出されるまで、洗浄を繰り返します。

例の説明

_ MRO: D,B,C,A,object  
_
  1. super(D, self).__init__()が呼び出されます。 isinstance(self、D)=> True
  2. Dの右側のクラスのMROでnext methodを検索します。

    _B.__init___が見つかり、呼び出されました


  1. _B.__init___はsuper(B, self).__init__()を呼び出します。

    isinstance(self、B)=> False
    isinstance(self、D)=> True

  2. したがって、MROは同じですが、検索はBの右側に続きます。つまり、C、A、オブジェクトが1つずつ検索されます。次に見つかった___init___が呼び出されます。

  3. 等々。

superの説明
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
superを使用するときに注意すること
http://fuhm.net/super-harmful/
Python MROアルゴリズム:
http://www.python.org/download/releases/2.3/mro/
superのドキュメント:
http://docs.python.org/library/functions.html
このページの下部には、superの[Nice]セクションがあります
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

これで問題が解決することを願っています。

15
batbrat

コードをこれに変更すると、説明がわかると思います(おそらくsuperB__mro__?):

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

実行すると、次のように表示されます。

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

また、チェックアウトする価値もあります PythonのSuperは気の利いたものですが、使用できません

34

推測するだけです:

4つのメソッドすべてのselfは、同じオブジェクト、つまりクラスDを参照します。そのため、B.__init__()では、super(B,self)への呼び出しはselfのダイヤモンドの祖先全体を認識しており、「後」からメソッドをフェッチする必要がありますB。この場合は、Cクラスです。

6
Javier

super()fullクラス階層を認識しています。これはBのinit内で何が起こるかです:

_>>> super(B, self)
<super: <class 'B'>, <D object>>
_

これは中心的な問題を解決し、

dのinit定義でsuperによって返されたオブジェクトは、Bのinit定義でsuperによって返されたオブジェクトにCの存在をどのように伝えますか?

つまり、Bのinit定義では、selfDのインスタンスであるため、Cの存在を伝えます。たとえば、Ctype(self).__mro__にあります。

3
hrr

Jacobの答えは問題を理解する方法を示し、batbratの答えは詳細とhrrの問題を直接示しています。

彼らがあなたの質問から(少なくとも明示的には)カバーしない1つのことはこの点です:

ただし、Bのinit関数でsuperの呼び出しをコメント化すると、AもCのinit関数も呼び出されません。

それを理解するには、以下のように、JacobのコードをAのinitにスタックを出力するように変更します。

import traceback

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__
        traceback.print_stack()

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

BCのベースクラスではないため、Bの行super(B, self).__init__()が実際にC.__init__()を呼び出しているのを見ると、少し意外です。

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
  File "/tmp/jacobs.py", line 31, in <module>
    x = D()
  File "/tmp/jacobs.py", line 29, in __init__
    super(D, self).__init__()
  File "/tmp/jacobs.py", line 17, in __init__
    super(B, self).__init__()
  File "/tmp/jacobs.py", line 23, in __init__
    super(C, self).__init__()
  File "/tmp/jacobs.py", line 11, in __init__
    traceback.print_stack()

これは、super (B, self)が 'Bのベースクラスバージョンの__init__を呼び出していないために発生します。代わりに、 'B__init__に存在し、そのような属性を持つselfの右側の最初のクラスで__mro__を呼び出すです。

したがって、Bのinit関数でのsuperの呼び出しをコメント化の場合、メソッドスタックはB.__init__で停止し、CまたはAに到達することはありません。

要約する:

  • どのクラスがそれを参照しているかに関係なく、selfは常にインスタンスへの参照であり、その__mro__および__class__は一定のままです
  • super()は、__mro__で現在のクラスの右側にあるクラスを探すメソッドを見つけます。 __mro__は一定のままであるため、ツリーやグラフとしてではなく、リストとして検索されます。

その最後の点で、MROのアルゴリズムの完全な名前はC3 superclass linearizationであることに注意してください。つまり、その構造をリストにフラット化します。さまざまなsuper()呼び出しが発生した場合、それらは効果的にそのリストを繰り返します。

2
caxcaxcoatl