Pythonの“ super”の仕組み正しいことをします?
-
03-07-2019 - |
質問
Python 2.5を実行しているため、この質問はPython 3には適用されない可能性があります。最も派生したクラスのコンストラクターを呼び出し、次にその親クラスを左から右に一覧表示し、次に祖父母を呼び出します。 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から返されたオブジェクトは、Cの存在をBのinit定義でsuperから返されたオブジェクトにどのように伝えますか?スーパー使用の後続の呼び出しがオブジェクト自体に保存する情報はありますか?もしそうなら、なぜself.superの代わりにsuperではないのですか?
編集:Jekkeは、superはクラスのインスタンスではなくクラスの属性であるため、self.superではないことを非常に正しく指摘しました。概念的にはこれは理にかなっていますが、実際にはスーパーはクラスの属性でもありません!これをインタープリターでテストするには、AとBの2つのクラスを作成します。BはAから継承し、 dir(B)
を呼び出します。 super
または __ super __
属性はありません。
解決
以下のリンクを提供します。これらのリンクは、私が期待する以上に詳細かつ正確にあなたの質問に答えます。ただし、時間を節約するために、あなた自身の言葉でもあなたの質問に答えます。ポイントに入れます-
- superは属性ではなく、組み込み関数です。
- Pythonのすべての type (クラス)には
__ mro __
属性があり、特定のインスタンスのメソッド解決順序を格納します。 - 各superの呼び出しは、super(type [、object-or-type])の形式です。 2番目の属性が今のところオブジェクトであると仮定しましょう。
- スーパーコールの開始点では、オブジェクトはDerivedクラスのタイプです( DC など)。
- superは、MROのクラスで、最初の引数として指定されたクラス(この場合はDCの後のクラス)の後に、一致するメソッド(この場合は
__ init __
)を探します。 - 一致するメソッドが見つかると(クラス BC1 で)、呼び出されます。
(このメソッドはsuperを使用する必要があるため、使用することを前提としています-Pythonのsuperは気の利いたものですが、使用できない-以下のリンクを参照してください) そのメソッドは、オブジェクトのクラスのMROで、 BC1 の右側にある次のメソッドを検索します。 - すべてのメソッドが検出されて呼び出されるまで、洗浄を繰り返します。
例の説明
MRO: D,B,C,A,object
-
super(D、self).__ init __()
が呼び出されます。 isinstance(self、D)=>正しい -
Dの右側のクラスのMROで next method を検索します。
B .__ init __
が見つかり、呼び出された
-
B .__ init __
は、super(B、self).__ init __()
を呼び出します。isinstance(self、B)=> False
isinstance(self、D)=> True -
したがって、MROは同じですが、検索はBの右側まで続きます。つまり、C、A、オブジェクトは1つずつ検索されます。次に見つかった
__ init __
が呼び出されます。 -
などなど。
スーパーの説明
http://www.python.org/download/releases/2.2 .3 / descrintro /#cooperation
スーパーを使用する際の注意事項
http://fuhm.net/super-harmful/
Pythons MROアルゴリズム:
http://www.python.org/download/releases/2.3/mro/
スーパーのドキュメント:
http://docs.python.org/library/functions.html
このページの下部には、スーパーに関する素晴らしいセクションがあります:
http://docstore.mik.ua/ orelly / other / python / 0596001886_pythonian-chp-5-sect-2.html
これが解決に役立つことを願っています。
他のヒント
コードをこれに変更すると、説明ができると思います(おそらく super
は __ mro __ の
B
がどこにあるのでしょうか? code>?):
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は気の利いたものですが、使用することはできません。 / p>
単なる推測:
4つのメソッドすべての self
は、同じオブジェクト、つまりクラス D
を参照します。
そのため、 B .__ init __()
で、 super(B、self)
の呼び出しは、 self
のダイヤモンドの祖先全体を認識し、 'after' B
からメソッドを取得します。この場合、それは C
クラスです。
super()
は full クラス階層を知っています。これは、Bのinit内で行われます:
>>> super(B, self)
<super: <class 'B'>, <D object>>
これは中心的な問題を解決します
Dのinit定義でsuperから返されたオブジェクトは、Bのinit定義でsuperから返されたオブジェクトにCの存在をどのように伝えるのですか?
つまり、Bのinit定義では、 self
は D
のインスタンスであるため、 C
の存在を伝えます。たとえば、 C
は type(self).__ mro __
にあります。
Jacobの答えは問題の理解方法を示し、batbratの答えは詳細を示し、hrrの答えは要点を示しています。
彼らがあなたの質問から(少なくとも明示的には)カバーしていないことの一つは、この点です:
ただし、Bのinit関数でsuperの呼び出しをコメントアウトすると、AもCのinit関数も呼び出されません。
それを理解するために、以下のようにAのinitにスタックを印刷するようにJacobのコードを変更します:
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()
B
の行 super(B、self).__ init __()
が実際に C .__ init __()を呼び出しているのを見るのは少し驚きです
。 C
は B
のベースクラスではありません。
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 __
」を呼び出していないために発生します。代わりに、 self
のに存在する
にそのような属性があります。 B
の右側にある最初のクラスで __ init __
を呼び出します__mro __
したがって、Bのinit関数でsuperへの呼び出しをコメントアウトすると、メソッドスタックは B .__ init __
で停止し、 Cには到達しません。
または A
。
要約するには:
- どのクラスがそれを参照しているかに関係なく、
self
は常にインスタンスへの参照であり、その__ mro __
および__ class __
は一定のままです - super()は、
__ mro __
の現在のクラスの右側にあるクラスを検索するメソッドを見つけます。__ mro __
は一定のままであるため、ツリーまたはグラフではなくリストとして検索されます。
最後の点で、MROのアルゴリズムのフルネームは C3スーパークラス線形化であることに注意してください。つまり、その構造をリストにフラット化します。さまざまな super()
呼び出しが発生すると、それらはそのリストを効果的に繰り返します。