何が違いますか:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
そして:
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()
は他のクラスがあなたが書いているクラスを使うことをより簡単にします。
Bob Martinが言っているように、良いアーキテクチャはあなたができるだけ長く意思決定を延期することを可能にします。
super()
はそのようなアーキテクチャを可能にします。
他のクラスがあなたが書いたクラスをサブクラス化するとき、それは他のクラスから継承することもできます。そしてそれらのクラスは、メソッド解決のためのクラスの順序に基づいて、この__init__
の後に来る__init__
を持つことができます。
super
がなければ、あなたは書いているクラスの親をハードコードするでしょう(例のように)。これは、MRO内の次の__init__
を呼び出さないことを意味します。したがって、その中のコードを再利用することはできません。
あなたが個人的な使用のためにあなた自身のコードを書いているなら、あなたはこの区別を気にしないかもしれません。しかし、他の人に自分のコードを使用させたい場合は、super
を使用すると、コードのユーザーにとってより大きな柔軟性が得られます。
これは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
を指定せずに、親で定義されたメソッドがその順序で次のメソッドを見つけるには、次のようにする必要があります。
UnsuperChild
はInjectMe
にアクセスできません。 「常にsuper
の使用を避ける」という結論にならないのはなぜですか?私はここで何が足りないのですか?
UnsuperChild
はnotがInjectMe
にアクセスできます。 UnsuperInjector
へのアクセス権を持つのはInjectMe
ですが、UnsuperChild
から継承したメソッドからそのクラスのメソッドを呼び出すことはできません。
両方の子クラスは、MROの次にくる同じ名前でメソッドを呼び出すことを意図しています。これは、いつ作成されたのか認識していなかったanotherクラスの場合があります。
super
のないものは、その親のメソッドをハードコードしています - そのため、そのメソッドの動作は制限されており、サブクラスはコールチェーンに機能を注入することはできません。
withsuper
の方が高い柔軟性があります。メソッドのコールチェーンを傍受して機能を注入することができます。
あなたはその機能を必要としないかもしれませんが、あなたのコードのサブクラスは必要かもしれません。
ハードコーディングするのではなく、常にsuper
を使用して親クラスを参照してください。
あなたが意図しているのは、次の行にある親クラスを参照することです。特に、子が継承しているのを見ることはできません。
super
を使用しないと、コードのユーザーに不要な制約を課す可能性があります。
これらすべてが、基本クラスが新しいスタイルのクラスであると想定しているのではありませんか?
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()
を呼び出すとき、最初の引数としてスコープが入っている現在のクラスを渡して、解決しようとしている親のスコープを示します。 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
を使う例えば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)
多くのすばらしい答えがありますが、視覚学習者の場合:まず、スーパーへの引数を使用して探索し、次にスーパーを使用せずに探索します。
インスタンス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は左から右です。
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
を渡すことで、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.__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