680

次の違いは何ですか:

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

と:

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

super単一継承のみのクラスでかなり多く使用されているのを見てきました。多重継承で使用する理由はわかりますが、この種の状況で使用する利点については不明です。

4

11 に答える 11

408

違いは何ですか?

SomeBaseClass.__init__(self) 

を呼び出すことを意味しSomeBaseClassます__init__。その間

super().__init__()

インスタンスのメソッド解決順序 (MRO) で の子クラス (このメソッドを定義するクラス)__init__に続く親クラスからバインドを呼び出すことを意味します。SomeBaseClass

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

簡単に説明

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

Bob Martin が言うように、優れたアーキテクチャでは、意思決定を可能な限り先延ばしにすることができます。

super()そのようなアーキテクチャを可能にすることができます。

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

そうしないsuperと、作成しているクラスの親をハードコードする可能性があります (例のように)。これは、MRO で next を呼び出さないことを意味する__init__ため、MRO 内のコードを再利用できなくなります。

個人的な使用のために独自のコードを作成している場合、この区別は気にしないかもしれません。しかし、他のユーザーに自分のコードを使用してもらいたい場合は、使用するsuperことで、コードのユーザーの柔軟性が向上します。

Python 2 対 3

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

super(Child, self).__init__()

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

super().__init__()

スタックフレームを上に移動し、メソッドへの最初の引数を取得し (通常selfはインスタンスメソッドまたはclsクラスメソッドですが、他の名前である可能性があります) Child、自由変数 (__class__メソッド内の自由クロージャ変数として名前で検索されます)。

以前は相互互換性のある の使用方法を示すことを好んsuperでいましたが、Python 2 はほとんど非推奨になっているため、Python 3 の方法、つまりsuper引数なしで呼び出す方法を示します。

前方互換性を持つインダイレクション

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

熟練した開発者にとって、前方互換性は非常に重要です。コードを変更しても最小限の変更で動作し続ける必要があります。改訂履歴を見るとき、何がいつ変更されたかを正確に知りたいと思うでしょう。

単一の継承から始めることもできますが、別の基本クラスを追加する場合は、基本の行を変更するだけで済みます。継承元のクラスで基本が変更された場合 (たとえば、ミックスインが追加された場合) は、変更する必要があります。このクラスには何もありません。

Python 2 では、引数を取得しsuperて正しいメソッド引数を正しく取得するのは少し混乱する可能性があるため、Python 3 のみの呼び出し方法を使用することをお勧めします。

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().__init__()

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

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

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

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

>>> 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

どちらの Child クラスも、MRO で次に来る同じ名前のメソッドを呼び出すつもりです。これは、作成時に認識されていなかった別のクラスである可能性があります。

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

がある方 super柔軟性があります。メソッドの呼び出しチェーンを傍受し、機能を注入することができます。

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

結論

super親クラスをハードコーディングするのではなく、常に参照するために使用します。

あなたが意図しているのは、特に子が継承しているのを目にするものではなく、次の行にある親クラスを参照することです。

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

于 2015-11-02T00:53:24.423 に答える
347

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

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

于 2008-10-21T18:24:50.463 に答える
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)

于 2008-10-22T00:06:32.333 に答える
22

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

クラス階層AB、およびC各クラスはそれに続くクラスの親でありa、 、b、および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

superstaticmethod での使用

たとえば、メソッド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)

説明:

__new__()1-呼び出し元クラスへの参照を最初のパラメータとして使用するのが通常ですが、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()子クラスを渡します。次に、対象のオブジェクトへの参照を渡します。この場合は、呼び出されたときに渡されたクラス参照です。ほとんどの場合、子クラスへの参照でもあります。場合によっては、複数世代の継承の場合など、そうでない場合もあります。AA.__new__(cls)

super(A, cls)

3- は原則として__new__()staticmethod であるためsuper(A, cls).__new__、 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)

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

super(A, self)

3-呼び出しは、スコープを解決し、それを親クラスのインスタンスであるかのようにsuper(A, self)適用するプロキシを返します。selfその proxy を呼び出しましょう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(...)参照を暗黙的に渡します。clsAalternate_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)
于 2016-09-07T17:33:28.780 に答える
0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

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

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

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

Child インスタンスが作成されると、その MRO(Method Resolution Order) は、継承に基づいて (Child、SomeBaseClass、object) の順序になります。( SomeBaseClass には、既定のオブジェクトを除いて他の親がないと仮定します)

を渡すことにより、インスタンスの MRO を検索し、Child, selfChild の次のプロキシ オブジェクトを返します。この場合は SomeBaseClass であり、このオブジェクトは SomeBaseClass のメソッドを呼び出します。つまり、 の場合、返されるプロキシ オブジェクトは次のようになります。superself__init__super(SomeBaseClass,self)superobject

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

于 2018-07-17T19:14:11.403 に答える