Pythonで「スーパー」は何をしますか?


564

違いは何ですか:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

そして:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

super継承が1つしかないクラスで非常に多く使用されているのを見てきました。なぜ多重継承で使用するのかはわかりますが、このような状況で使用することの利点についてははっきりしません。

回答:


309

の利点 super()単一継承は最小限です。ほとんどの場合、親クラスを使用するすべてのメソッドに基本クラスの名前をハードコードする必要はありません。

ただし、なしで多重継承を使用することはほとんど不可能super()です。これには、ミックスイン、インターフェース、抽象クラスなどの一般的なイディオムが含まれます。これは、後で拡張するコードにも拡張されます。後で誰かが拡張されたクラスとChildミックスインを作成したい場合、そのコードは正しく機能しません。


6
「適切に機能しない」とはどういう意味ですか?
チャーリーパーカー、

319

違いは何ですか?

SomeBaseClass.__init__(self) 

呼び出すための手段SomeBaseClassさん__init__。ながら

super(Child, self).__init__()

インスタンスのメソッド解決順序(MRO)に__init__続く親クラスから境界を呼び出すことを意味しますChild

インスタンスが子のサブクラスである場合、MROの次に来る別の親が存在する可能性があります。

簡単に説明

クラスを作成するとき、他のクラスがそれを使用できるようにする必要があります。super()作成中のクラスを他のクラスが簡単に使用できるようにします。

ボブマーティンが言うように、優れたアーキテクチャでは、意思決定を可能な限り延期することができます。

super() この種のアーキテクチャを実現できます。

作成したクラスを別のクラスがサブクラス化する場合、他のクラスから継承している可能性もあります。そしてそれらのクラスは__init__この後に来る__init__、メソッド解決のためのクラスの順序に基づいて、ます。

なしsuperあなたはおそらくハードコードは、クラスの親、あなたが書いているでしょう(例はないように)。これは__init__、MROでnext を呼び出さないことを意味します。したがって、その中でコードを再利用することはできません。

個人使用のために独自のコードを作成している場合、この違いを気にする必要はありません。ただし、他の人にコードを使用してもらいたい場合superは、使用することで、コードのユーザーの柔軟性が向上します。

Python 2対3

これはPython 2および3で機能します。

super(Child, self).__init__()

これはPython 3でのみ機能します。

super().__init__()

スタックフレーム内を上に移動してメソッドの最初の引数を取得し(通常selfはインスタンスメソッドまたはclsクラスメソッドの場合-他の名前でもかまいません)、引数なしで機能しChild、フリー変数(名前で検索されます__class__メソッド内のフリークロージャ変数としてます)。

私は相互互換性のあるの使用方法を示すことを好みsuperますが、Python 3のみを使用している場合は、引数なしで呼び出すことができます。

前方互換性を持つ間接参照

それはあなたに何を与えるのですか?単一継承の場合、質問の例は静的分析の観点からは実質的に同じです。ただし、superと、前方互換性のある間接層が提供されます。

経験豊富な開発者にとって、上位互換性は非常に重要です。コードを変更しても、最小限の変更でコードが機能し続けるようにする必要があります。変更履歴を見るとき、いつ何が変更されたかを正確に確認したいとします。

単一の継承から始めることもできますが、別の基本クラスを追加する場合は、基本との行を変更するだけで済みます-継承元のクラスで基本が変更された場合(たとえば、ミックスインが追加された場合)、変更しますこのクラスには何もありません。特にPython 2ではsuper、正しい引数と正しい引数を取得するのが難しい場合があります。super単一継承で正しく使用していることがわかっている場合は、これによりデバッグが難しくなりません。

依存性注入

他の人々はあなたのコードを使用して、親をメソッド解決に注入することができます:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

オブジェクトに別のクラスを追加し、FooとBarの間にクラスを挿入するとします(テストまたはその他の理由により)。

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

un-super子を使用すると、依存関係の注入に失敗します。これは、使用している子が、自身の後に呼び出されるメソッドをハードコーディングしているためです。

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

ただし、を使用superする子を持つクラスは、依存関係を正しく挿入できます。

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

コメントへの対処

なぜこれが便利なのでしょうか?

Pythonは、C3線形化アルゴリズムを介して複雑な継承ツリーを線形化し、メソッド解決順序(MRO)を作成します。

メソッドをこの順序で検索する必要があります。

親で定義されたメソッドがなしでその順序で次のメソッドを見つけるためにはsuper

  1. インスタンスのタイプからmroを取得します
  2. メソッドを定義するタイプを探します
  3. メソッドで次のタイプを見つける
  4. そのメソッドをバインドし、予想される引数で呼び出します

UnsuperChildへのアクセス権を持つべきではありませんInjectMe。「常に使用しない」という結論にならないのはなぜsuperですか?ここで何が欠けていますか?

UnsuperChildではないへのアクセス権を持っていますInjectMe。それはにUnsuperInjectorアクセスできますが、InjectMe継承元のメソッドからそのクラスのメソッドを呼び出すことはできませんUnsuperChild

両方の子クラスは、MROで次に来る同じ名前でメソッドを呼び出すことを意図しています。これは、作成時に認識されていなかった別のクラスである可能性があります。

super親のメソッドをハードコードしていないもの-したがって、そのメソッドの動作が制限されており、サブクラスは呼び出しチェーンに機能を注入できません。

1 とは、 superより高い柔軟性を持っています。メソッドの呼び出しチェーンを傍受し、機能を注入することができます。

その機能は必要ないかもしれませんが、コードのサブクラスは必要かもしれません。

結論

super親クラスをハードコーディングする代わりに、常に使用して親クラスを参照します。

あなたが意図しているのは、次の行である親クラスを参照することです。具体的には、子が継承しているのを見ているクラスではありません。

使用しないsuperと、コードのユーザーに不要な制約が課される可能性があります。


Cにおいて、DIは次のようであるこの。コードはこちらlistインターフェースの実装をもう1つ追加するとdoublylinkedlist、アプリケーションはそれをスムーズに選択します。config.txtロード時に実装を導入およびリンクすることで、サンプルをより構成可能にすることができます。これは正しい例ですか?はいの場合、どのようにコードを関連付けますか?wikiのDIの最初のadvを参照してください。新しい実装はどこで構成可能ですか?あなたのコードで
オーバーエクスチェンジ

たとえば、「インジェクター」クラスの1つがクラスから継承する場合、継承によって新しい実装が作成されInjectMeます。コメントは議論の対象ではないので、チャットで他の人とさらに議論するか、メインサイトで新しい質問をすることをお勧めします。
アーロンホール

すばらしい答えです!ただし、多重継承を使用する場合、super()と__init__関数には複雑な問題があります。特に、シグネチャが__init__階層内のクラス間で異なる場合。この側面に焦点を当てた回答を追加しました
Aviad Rozenhek

35

私は少し遊んだ super()呼び出し順序を変更できることを認識していました。

たとえば、次の階層構造があります。

    A
   / \
  B   C
   \ /
    D

この場合、DのMROは(Python 3のみ)になります。

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

super()メソッドの実行後に呼び出すクラスを作成しましょう。

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / 
  B  C
    /
    D

したがって、解決の順序がMROと同じであることがわかります。しかしsuper()、メソッドの最初で呼び出すと:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

MROタプルの順序を逆にした、別の順序があります。

    A
   / 
  B  C
    /
    D 

さらに読むには、次の回答をお勧めします。

  1. スーパー(大階層)を使用したC3線形化の例
  2. 古いスタイルクラスと新しいスタイルクラスの間の重要な動作の変更
  3. 新しいスタイルのクラスの裏話

注文が変更される理由がわかりません。最初の部分は、Dが最初のクラスであるためDBCAであることを理解し、ロード時にself(B、C)は最終的にB、C、そしてAのみを出力します。部。私がこの理解に従っている場合、2番目の部分はBCADのようにすべきではありませんか?少し説明してください。
JJson 2018年

私の悪いことに、すべての各クラスインスタンスが最初にsuper()で開始されていることに気付きませんでした。それがそうである場合、それはABCDであるべきではありませんか?私はどういうわけかACBDがどのようになってきたのか理解していますが、それでも納得できず、まだ少し混乱しています。私の理解では、d = D()は2つのセルフパラメータを使用してクラスD(B、C)を呼び出しました.super()が最初に開始され、次にBがその属性と一緒に呼び出されるため、DはCの前に出力されず、クラスはD(B、C)には2つのセルフパラメータが含まれているため、2つ目のクラスC(A)を実行する必要があります。実行後、実行するセルフパラメータはもうありません
JJson

それはmroの定義に基づいています。
SKhalymon 2018年

1
次にCを印刷し、次にBを印刷し、最後にDを印刷します。
JJson 2018年

2
最初のものを入手する限り、2番目のものを理解することは非常に簡単です。それはスタックのようなものです。印刷をスタックにプッシュして、super()を実行します。Aが完了すると、スタックに印刷が開始されるため、順序が逆になります。
付与日

35

これはすべて、基本クラスが新しいスタイルのクラスであることを前提としていますか?

class A:
    def __init__(self):
        print("A.__init__()")

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

Python 2では機能しclass Aません。新しいスタイルでなければなりません。つまり、class A(object)


20

super()classmethod、インスタンスメソッド、またはstaticmethodの親のバージョンに解決するために呼び出すときに、現在のクラスのスコープを最初の引数として渡して、解決しようとしている親のスコープを示します。 2番目の引数は、対象のオブジェクトであり、そのスコープを適用しようとしているオブジェクトを示します。

クラス階層を考えてみましょうABと、Cどこ各クラスには、それに続く1の親である、とabと、cそれぞれのそれぞれのインスタンス。

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

super静的メソッドでの使用

たとえばsuper()__new__()メソッド内から使用する

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

説明:

1- __new__()呼び出し元のクラスへの参照を最初のパラメーターとして取るのは通常ですが、Pythonではクラスメソッドとしてではなく、静的メソッドとして実装されます。つまり、クラスへの参照は、__new__()直接呼び出すときに最初の引数として明示的に渡す必要があります。

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- super()親クラスに到達するために呼び出すときにA、最初の引数として子クラスを渡します。次に、対象のオブジェクトへの参照を渡します。この場合A.__new__(cls)は、呼び出し時に渡されたクラス参照です。ほとんどの場合、それはたまたま子クラスへの参照でもあります。たとえば、複数世代の継承の場合など、状況によってはそうでない場合があります。

super(A, cls)

原則としてので3- __new__()staticmethodあり、super(A, cls).__new__この場合も、insterestのオブジェクトへの参照を含む、明示的にすべての引数を、供給されるstaticmethodとニーズを戻しますcls

super(A, cls).__new__(cls, *a, **kw)

4-同じことをせずに super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

superインスタンスメソッドでの使用

例:super()内部から使用する__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

説明:

1- __init__はインスタンスメソッドです。つまり、最初の引数としてインスタンスへの参照を受け取ります。インスタンスから直接呼び出されると、参照は暗黙的に渡されます。つまり、指定する必要はありません。

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- super()__init__()で呼び出す場合、最初の引数として子クラスを渡し、2番目の引数として対象のオブジェクトを渡します。これは一般に、子クラスのインスタンスへの参照です。

super(A, self)

3-呼び出しsuper(A, self)は、スコープを解決してそれselfが親クラスのインスタンスであるかのように適用するプロキシを返します。そのプロキシを呼び出しましょうs。以来は、__init__()呼び出しがインスタンスメソッドでs.__init__(...)暗黙的に参照を渡すself親の最初の引数として__init__()

4- superインスタンスへの参照を明示的に親のバージョンのに渡す必要なしに同じことを行うには__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

superクラスメソッドで使用する

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

説明:

1-クラスメソッドはクラスから直接呼び出すことができ、最初のパラメータとしてクラスへの参照を受け取ります。

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- super()クラスメソッド内で呼び出して、その親のバージョンに解決するとき、現在の子クラスを最初の引数として渡して、解決しようとしている親のスコープを示し、対象のオブジェクトを2番目の引数として渡しますそのスコープを適用するオブジェクトを示します。これは一般に、子クラス自体またはそのサブクラスの1つへの参照です。

super(B, cls_or_subcls)

3-呼び出しsuper(B, cls)はのスコープに解決され、にA適用されclsます。ので、alternate_constructor()クラスメソッドであるコールは、super(B, cls).alternate_constructor(...)暗黙の基準に合格するclsの最初の引数としてAののバージョンalternate_constructor()

super(B, cls).alternate_constructor()

4-を使用せずに同じことを行うにはsuper()、の非バインドバージョンA.alternate_constructor()(つまり、関数の明示的なバージョン)への参照を取得する必要があります。これを行うだけでは機能しません。

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

A.alternate_constructor()メソッドは暗黙的な参照をA最初の引数として取るため、上記は機能しません。ここclsで渡されるのは、その2番目の引数です。

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

6

多くの素晴らしい答えですが、視覚的な学習者向けです。最初に、superへの引数付きで、次になしで探索しましょう。 スーパー継承ツリーの例

画像から緑色で示されている継承チェーンを持つjackクラスから作成されたインスタンスがあるとしますJack。呼び出し:

super(Jack, jack).method(...)

jack(その継承ツリーを特定の順序で)のMRO(メソッド解決順序)を使用し、から検索を開始しJackます。なぜ親クラスを提供できるのですか?インスタンスから検索を開始するとjackすると、インスタンスメソッドが見つかります。全体のポイントは、親メソッドを見つけることです。

スーパーに引数を指定しない場合、最初に渡される引数はのクラスでselfあり、2番目に渡される引数はselfです。これらはPython3で自動的に計算されます。

ただしJack、のメソッドを使用したくない場合は、渡すのでJackはなく、から渡しJenてメソッドの上方検索を開始することができJenます。

一度に1つのレイヤーを検索します(深さではなく幅)。たとえばAdamSue両方に必要なメソッドがある場合、Sue最初のレイヤーが最初に見つかります。

場合CainSueの両方が必要な方法で、持っていたCainのメソッドが最初に呼び出されます。これはコードで次のように対応します:

Class Jen(Cain, Sue):

MROは左から右です。


2

ここでいくつかの素晴らしい答えがsuper()ありますが、階層の異なるクラスが異なるシグネチャを持っている場合の使用方法には取り組みません...特に__init__

その部分に答えて効果的に使用できるようにするにsuper()は、私の答えのsuper()を読んで、協調メソッドの署名を変更することをお勧めします

このシナリオの解決策は次のとおりです。

  1. 階層の最上位クラスは、次のようなカスタムクラスから継承する必要がありますSuperObject
  2. クラスが異なる引数を取ることができる場合は、常に受け取ったすべての引数をキーワード引数としてスーパー関数に渡し、常に受け入れ**kwargsます。
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

使用例:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

出力:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

これはかなり理解しやすいです。

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

では、使用するとどうなりますsuper(Child,self)か?

子インスタンスが作成されると、そのMRO(メソッド解決順序)は、継承に基づいて(子、SomeBaseClass、オブジェクト)の順序になります。(SomeBaseClassには、デフォルトオブジェクトを除いて他の親がないと仮定します)

渡すことによってChild, selfsuperのMROで検索selfインスタンスを、次の子のプロキシオブジェクトを返す、この場合には、それはSomeBaseClassだ、このオブジェクトは、呼び出し__init__SomeBaseClassの方法を。つまり、の場合super(SomeBaseClass,self)super返されるプロキシオブジェクトは次のようになります。object

多重継承の場合、MROには多くのクラスを含めることができるため、基本的superにはMROのどこから検索を開始するかを決定できます。


0

次のコードを検討してください。

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

のコンストラクタをYに変更するとX.__init__、次のようになります。

X
Y
Q

しかし、を使用するとsuper(Y, self).__init__()、次のようになります。

X
P
Y
Q

そして、PまたはQも、あなたが書くとき、あなたが知らない別のファイルから関与している可能性があるXY。したがって、基本的に、Yのシグネチャはと同じくらい簡単super(Child, self)ですが、書いているときに何を参照するかはわかりません。だからこそ、superの方が適しているでしょう。class Y(X)Y(X)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.