Pythonで「スーパー」は何をしますか?
-
03-07-2019 - |
質問
違いは何ですか:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
and:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
単一の継承のみを持つクラスで super
が非常に多く使用されているのを見てきました。なぜ多重継承で使用するのかはわかりますが、このような状況でそれを使用する利点は不明です。
解決
単一継承での super()
の利点は最小限です。ほとんどの場合、親メソッドを使用するすべてのメソッドに基本クラスの名前をハードコーディングする必要はありません。
ただし、 super()
なしで多重継承を使用することはほとんど不可能です。これには、ミックスイン、インターフェイス、抽象クラスなどの一般的なイディオムが含まれます。これは、後でコードを拡張するコードに拡張されます。誰かが後で Child
とmixinを拡張したクラスを書きたいと思った場合、彼らのコードは正しく動作しません。
他のヒント
違いは何ですか?
SomeBaseClass.__init__(self)
は、 SomeBaseClass
の __ init __
を呼び出すことを意味します。一方
super(Child, self).__init__()
は、インスタンスのメソッド解決順序(MRO)で Child
に続く親クラスからバインドされた __ init __
を呼び出すことを意味します。
インスタンスがChildのサブクラスである場合、MROで次に来る別の親が存在する可能性があります。
簡単に説明
クラスを記述するとき、他のクラスがそれを使用できるようにする必要があります。 super()
を使用すると、他のクラスが作成中のクラスを簡単に使用できます。
ボブ・マーティンが言うように、優れたアーキテクチャにより、意思決定を可能な限り延期することができます。
super()
は、そのようなアーキテクチャを有効にすることができます。
別のクラスが作成したクラスをサブクラス化する場合、他のクラスから継承することもできます。そして、これらのクラスには、メソッド解決のためのクラスの順序に基づいて、この __ init __
の後に来る __ init __
を含めることができます。
super
を使用しないと、作成中のクラスの親をハードコーディングする可能性が高くなります(例のように)。これは、MROで次の __ init __
を呼び出さないことを意味します。したがって、MRO内のコードを再利用することはできません。
個人的な使用のために独自のコードを書いている場合、この区別を気にかけないかもしれません。ただし、他の人にコードを使用させたい場合は、 super
を使用することで、コードのユーザーの柔軟性が高まります。
Python 2対3
これはPython 2および3で動作します
super().__init__()
これはPython 3でのみ機能します:
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__()
スタックフレーム内で上に移動し、メソッドの最初の引数(通常はインスタンスメソッドの場合は self
、クラスメソッドの場合は cls
を取得することにより、引数なしで動作します。 -しかし、他の名前の場合もあります)、フリー変数内でクラス(例: Child
)を見つけます(メソッド内のフリークロージャー変数として __ class __
という名前で検索されます) )。
super
を使用する相互互換性のある方法をデモンストレーションしますが、Python 3のみを使用している場合は、引数なしで呼び出すことができます。
前方互換性のあるインダイレクション
それはあなたに何を与えますか?単一継承の場合、静的分析の観点からは、質問の例は実質的に同一です。ただし、 super
を使用すると、前方互換性のある間接層が得られます。
経験豊富な開発者にとって、前方互換性は非常に重要です。コードは、変更しても最小限の変更で動作し続ける必要があります。改訂履歴を見るとき、何がいつ変更されたかを正確に確認する必要があります。
単一の継承から始めることもできますが、別の基本クラスを追加することにした場合は、その行をベースで変更するだけで済みます。このクラスでは何も変更しません。特にPython 2では、 super
の引数と正しいメソッド引数を正しく取得するのは難しい場合があります。単一の継承で super
を正しく使用していることがわかっている場合、デバッグをこれ以上難しくすることはありません。
依存性注入
他の人があなたのコードを使用して、メソッド解決に親を注入できます:
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
別のクラスをオブジェクトに追加し、FooとBarの間にクラスを挿入したい場合(テストまたはその他の理由で):
>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
使用している子が独自の後に呼び出されるメソッドをハードコーディングしているため、非スーパー子の使用は依存関係の挿入に失敗します。
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
ただし、 super
を使用する子を持つクラスは、依存関係を正しく挿入できます。
コメントのアドレス指定
なぜこれが便利なのでしょうか?
Pythonは複雑な継承を線形化します
このすべては、基本クラスが新しいスタイルのクラスであると想定していませんか?
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)
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
さらに読むには、次の回答をお勧めします。
super()
を呼び出して、親のバージョンのclassmethod、instance method、またはstaticmethodに解決する場合、指定したスコープを持つ最初の引数として現在のクラスを渡したい解決しようとしている親のスコープ、および2番目の引数として、そのスコープを適用しようとしているオブジェクトを示す対象オブジェクト。
クラス階層 A
、 B
、および 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
静的メソッドで super
を使用する
e.g。 __ new __()
メソッド内から super()
を使用する
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ではクラスメソッドとして実装されていません むしろstaticmethodです。つまり、 __ 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 __
もstaticmethodを返し、すべてを指定する必要がありますinsterestのオブジェクトへの参照を含む明示的な引数、この場合は 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
を使用する
e.g。 __ init __()
super()
を使用する
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- __ init __()
内で super()
を呼び出すとき、子クラスを最初の引数として、関心のあるオブジェクトを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- classmethodはクラスから直接呼び出すことができ、最初のパラメーターとしてクラスへの参照を取ります。
# 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
のスコープに解決され、 clに適用されます
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
これはかなり理解しやすいです。
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
OK、 super(Child、self)
を使用するとどうなりますか?
Childインスタンスが作成されると、そのMRO(Method Resolution Order)は、継承に基づいて(Child、SomeBaseClass、object)の順序になります。 (SomeBaseClassには、デフォルトオブジェクトを除いて他の親がないと仮定します)
Child、self
、 super
を渡すことにより、 self
インスタンスのMROを検索し、Childの次のプロキシオブジェクトを返します。この場合はSomeBaseClassであり、このオブジェクトはSomeBaseClassの __ init __
メソッドを呼び出します。つまり、 super(SomeBaseClass、self)
の場合、 super
が返すプロキシオブジェクトは object
多重継承の場合、MROには多くのクラスを含めることができるため、基本的に super
を使用すると、MROで検索を開始する場所を決定できます。
ここでいくつかの優れた回答がありますが、階層内の異なるクラスが異なるシグネチャを持つ場合、特に __ init__の場合、
super()
の使用方法には取り組みません。
その部分に回答し、 super()
を効果的に使用できるようにするために、私の回答 super()および協調メソッドの署名の変更。
このシナリオの解決策は次のとおりです。
- 階層の最上位クラスは、
などのカスタムクラスから継承する必要がありますSuperObject
:- クラスが異なる引数を取ることができる場合、受け取ったすべての引数を常にキーワード引数としてスーパー関数に渡し、常に
** 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
多くの素晴らしい答えがありますが、視覚学習者向け: 最初にsuperの引数を使用して探索し、次にsuperを使用しないで探索します。
クラス Jack
から作成されたインスタンス jack
があると想像してください。このインスタンスは、図に緑色で示すような継承チェーンを持っています。呼び出し:
super(Jack、jack).method(...)
は、 jack
(特定の順序の継承ツリー)のMRO(メソッド解決順序)を使用し、 Jack
から検索を開始します。なぜ親クラスを提供できるのですか?インスタンス jack
から検索を開始すると、インスタンスメソッドが見つかります。全体のポイントは、親メソッドを見つけることです。
スーパーに引数を指定しない場合、最初に渡される引数は self
のクラスであり、2番目に渡される引数は self
です。これらはPython3で自動的に計算されます。
ただし、 Jack
を渡すのではなく、 Jack
のメソッドを使用したくない場合は、 Jen
を渡して開始できます Jen
からメソッドを上方に検索します。
一度に1つのレイヤー(深さではなく幅)を検索します。 Adam
と Sue
の両方に必要なメソッドがある場合、 Sue
からのメソッドが最初に見つかります。
Cain
と Sue
の両方に必要なメソッドがある場合、 Cain
のメソッドが最初に呼び出されます。
これはコードで次のように対応します:
Class Jen(Cain, Sue):
MROは左から右です。