web-dev-qa-db-ja.com

Pythonで 'super'は何をするのですか?

何が違いますか:

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

そして:

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

私はsuperが単一継承しかないクラスでかなり多く使われているのを見ました。私はなぜあなたが多重継承でそれを使うのか理解できるが、この種の状況でそれを使うことの利点が何であるかについては不明である。

489
user25785

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

しかし、super()なしで多重継承を使うことはほとんど不可能です。これには、ミックスイン、インターフェイス、抽象クラスなどの一般的な慣用句が含まれます。これは、後でコードを拡張するコードにも適用されます。後で誰かがChildとmixinを拡張したクラスを書きたがった場合、彼らのコードは正しく動作しません。

281
John Millikin

違いは何ですか?

SomeBaseClass.__init__(self) 

SomeBaseClass__init__を呼び出すことを意味します。しながら

super(Child, self).__init__()

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

インスタンスがChildのサブクラスである場合、MROの中に次に来る別の親があるかもしれません。

簡単に説明

クラスを書くとき、他のクラスがそれを使えるようにしたいと思うでしょう。 super()は他のクラスがあなたが書いているクラスを使うことをより簡単にします。

Bob Martinが言っているように、良いアーキテクチャはあなたができるだけ長く意思決定を延期することを可能にします。

super()はそのようなアーキテクチャを可能にします。

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

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

あなたが個人的な使用のためにあなた自身のコードを書いているなら、あなたはこの区別を気にしないかもしれません。しかし、他の人に自分のコードを使用させたい場合は、superを使用すると、コードのユーザーにとってより大きな柔軟性が得られます。

Python 2と3

これはPython 2と3で動作します。

super(Child, self).__init__()

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

super().__init__()

スタックフレーム内を上に移動してメソッドの最初の引数(通常はインスタンスメソッドの場合はself、クラスメソッドの場合はcls - ただし他の名前の場合もあります)を取得し、クラス内でクラス(例えばChild)を取得します。自由変数(メソッド内で自由閉包変数として__class__という名前で検索されます)。

私はsuperの相互互換性のある使い方を実演することを望みますが、もしあなたがPython 3だけを使っているなら、引数なしでそれを呼び出すことができます。

前方互換性のある間接指定

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

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

単一継承から始めることもできますが、別の基本クラスを追加することにした場合は、基本となる行を変更するだけで済みます。つまり、継承元のクラスで基本が変更された場合(mixinが追加されるなど)このクラスには何もありません。特に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

使用している子が独自のメソッドの後に呼び出されるメソッドをハードコードしているため、スーパーではない子を使用しても依存関係を注入できません。

>>> 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. そのメソッドをバインドし、期待される引数でそれを呼び出す

UnsuperChildInjectMeにアクセスできません。 「常にsuperの使用を避ける」という結論にならないのはなぜですか?私はここで何が足りないのですか?

UnsuperChildnotInjectMeにアクセスできます。 UnsuperInjectorへのアクセス権を持つのはInjectMeですが、UnsuperChildから継承したメソッドからそのクラスのメソッドを呼び出すことはできません。

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

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

withsuperの方が高い柔軟性があります。メソッドのコールチェーンを傍受して機能を注入することができます。

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

結論

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

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

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

261
Aaron Hall

これらすべてが、基本クラスが新しいスタイルのクラスであると想定しているのではありませんか?

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)である必要があります。

35
mhawke

私は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. 新スタイルの授業の裏話
27
Infernion

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

クラス階層AB、およびCを考えます。各クラスはそれに続くクラスの親、およびそれぞれの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__()は静的メソッドでもあるので、super(A, cls).__new__も静的メソッドを返し、そしてこの場合は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のバージョンのalternate_constructor()への最初の引数として暗黙のうちにAの参照を渡します。

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)
16
Michael Ekoka

多くのすばらしい答えがありますが、視覚学習者の場合:まず、スーパーへの引数を使用して探索し、次にスーパーを使用せずに探索します。 super inheritance tree example

インスタンス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は左から右です。

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

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

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

さて、あなたがsuper(Child,self)を使うとどうなりますか?

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

Child, selfを渡すことで、superselfインスタンスのMROを検索し、Childの次のプロキシオブジェクトを返します。この場合はSomeBaseClassです。このオブジェクトはSomeBaseClassの__init__メソッドを呼び出します。つまり、super(SomeBaseClass,self)の場合、superが返すプロキシオブジェクトはobjectになります。

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

0
dorisxx

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

その部分に回答し、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.__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
Aviad Rozenhek