superと言えばsuperみんなは簡単だと思うかもしれませんね。だって、親クラスのメソッドを呼び出すために使うだけじゃないですか。もし本当にそんなに簡単なら、この記事も存在しなかったでしょう。詳しく説明しますので、お聞きください。

約束

始める前に、この記事で使用するPythonのバージョンを決めておきましょう。デフォルトはPython 3です。つまり、この記事で定義するクラスはすべて新しいスタイルのクラスです。Python 2を使っている場合は、必ず継承してください。object:
# 默认, Python 3
class A:
    pass

# Python 2
class A(object):
    pass

Python 3とPython 2のもう一つの違いは、Python 3では直接使用できることですsuper().xxxの代わりにsuper(Class, self).xxx :

# 默认,Python 3
class B(A):
    def add(self, x):
        super().add(x)

# Python 2
class B(A):
    def add(self, x):
        super(B, self).add(x)

したがって、Python 2を使っている場合は、この記事のsuper()を置き換えてくださいsuepr(Class, self) 。

他にもPython 2と互換性のない状況があれば、記事中で注記します。

単一継承

単一継承ではsuperみんなが思っているように、主に親クラスのメソッドを呼び出すために使われます。

class A:
    def __init__(self):
        self.n = 2

    def add(self, m):
        print('self is {0} @A.add'.format(self))
        self.n += m


class B(A):
    def __init__(self):
        self.n = 3

    def add(self, m):
        print('self is {0} @B.add'.format(self))
        super().add(m)
        self.n += 3

以下のコードを実行した後、 b.nの値はいくつになると思いますか?

b = B()
b.add(2)
print(b.n)

実行結果は以下のとおりです:

self is <__main__.B object at 0x106c49b38> @B.add
self is <__main__.B object at 0x106c49b38> @A.add
8

この結果は2つのことを示しています:

  • 1、super().add(m)確かに親クラスAのaddメソッドを呼び出しています。
  • 2、super().add(m)親クラスのメソッドを呼び出すdef add(self, m)とき、このとき親クラス内のselfは親クラスのインスタンスではなく子クラスのインスタンスです。したがってb.add(2)の後の結果は5ではなく4 。

この結果はあなたの予想と同じでしょうか?次に多重継承の例を見てみましょう。

多重継承

今回はもう一つのclass Cともう一つのclass D:

class C(A):
    def __init__(self):
        self.n = 4

    def add(self, m):
        print('self is {0} @C.add'.format(self))
        super().add(m)
        self.n += 4


class D(B, C):
    def __init__(self):
        self.n = 5

    def add(self, m):
        print('self is {0} @D.add'.format(self))
        super().add(m)
        self.n += 5

今度は以下のコードは何を出力するでしょうか?

d = D()
d.add(2)
print(d.n)

今回の出力は以下のとおりです:

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @C.add
self is <__main__.D object at 0x10ce10e48> @A.add
19

あなたの予想は当たりましたか?おそらく上記のコードの出力が次のようになると考えていたでしょう:

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @A.add
15

なぜ予想と違うのでしょうか?次に一緒にsuperの謎を見ていきましょう。

superはクラス

私たちがsuper()を呼び出すとき、実際にはsuperクラスをインスタンス化しています。見間違いではありません、superはクラスであり、キーワードでも関数でも他のデータ構造でもありません:

>>> class A: pass
...
>>> s = super(A)
>>> type(s)
<class 'super'>
>>>

ほとんどの場合、superには2つの非常に重要な情報が含まれています: MROと、MRO内のクラスです。以下のようにsuper呼び出すとき:

super(a_type, obj)

MROとはtype(obj)のMROを指し、MRO内のそのクラスはa_type、同時にisinstance(obj, a_type) == True 。

このように呼び出すとき:

super(type1, type2)

MROはtype2のMRO, MRO内のそのクラスはtype1、同時にissubclass(type2, type1) == True 。

では、super()は実際に何をするのでしょうか?簡単に言うと、MROと、MRO内のクラスを提供します。C , super()は、MRO 中 Cの後のクラスからメソッドを探すオブジェクトを返します。

つまり、検索方法は通常のメソッドのようにすべてのMROクラスから探すのではなく、MROの tail から探します。

例を挙げると、MRO:

[A, B, C, D, E, object]

以下の呼び出し:

super(C, A).foo()

superはCの後からのみ検索します。つまり、D 或 E 或 object内でのみfooメソッドを検索します。

多重継承におけるsuperの動作方法

再び前述の

d = D()
d.add(2)
print(d.n)

に戻ります。今ならなぜ出力が

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @C.add
self is <__main__.D object at 0x10ce10e48> @A.add
19

になるのか、少しはわかったのではないでしょうか ;)

以下で具体的に分析してみましょう:

  • DのMROは:[D, B, C, A, object] 。 注記:を使用してD.mro()(Python 2ではD.__mro__) 確認できますDのMRO情報)

  • 詳細なコード分析は以下のとおりです:

    class A:
        def __init__(self):
            self.n = 2
    
        def add(self, m):
            # 第四步
            # 来自 D.add 中的 super
            # self == d, self.n == d.n == 5
            print('self is {0} @A.add'.format(self))
            self.n += m
            # d.n == 7
    
    
    class B(A):
        def __init__(self):
            self.n = 3
    
        def add(self, m):
            # 第二步
            # 来自 D.add 中的 super
            # self == d, self.n == d.n == 5
            print('self is {0} @B.add'.format(self))
            # 等价于 suepr(B, self).add(m)
            # self 的 MRO 是 [D, B, C, A, object]
            # 从 B 之后的 [C, A, object] 中查找 add 方法
            super().add(m)
    
            # 第六步
            # d.n = 11
            self.n += 3
            # d.n = 14
    
    class C(A):
        def __init__(self):
            self.n = 4
    
        def add(self, m):
            # 第三步
            # 来自 B.add 中的 super
            # self == d, self.n == d.n == 5
            print('self is {0} @C.add'.format(self))
            # 等价于 suepr(C, self).add(m)
            # self 的 MRO 是 [D, B, C, A, object]
            # 从 C 之后的 [A, object] 中查找 add 方法
            super().add(m)
    
            # 第五步
            # d.n = 7
            self.n += 4
            # d.n = 11
    
    
    class D(B, C):
        def __init__(self):
            self.n = 5
    
        def add(self, m):
            # 第一步
            print('self is {0} @D.add'.format(self))
            # 等价于 super(D, self).add(m)
            # self 的 MRO 是 [D, B, C, A, object]
            # 从 D 之后的 [B, C, A, object] 中查找 add 方法
            super().add(m)
    
            # 第七步
            # d.n = 14
            self.n += 5
            # self.n = 19
    
    d = D()
    d.add(2)
    print(d.n)

    呼び出しプロセスの図は以下のとおりです:

    D.mro() == [D, B, C, A, object]
    d = D()
    d.n == 5
    d.add(2)
    
    class D(B, C):          class B(A):            class C(A):             class A:
        def add(self, m):       def add(self, m):      def add(self, m):       def add(self, m):
            super().add(m)  1.--->  super().add(m) 2.--->  super().add(m)  3.--->  self.n += m
            self.n += 5   <------6. self.n += 3    <----5. self.n += 4     <----4. <--|
            (14+5=19)               (11+3=14)              (7+4=11)                (5+2=7)

これで d.add(2) の後に d.n の値が 19 になる理由がわかりましたね ;)

記事の出典:https://mozillazg.com/2016/12/python-super-is-not-as-simple-as-you-thought.html