Python のオーバライドと super
◆ メソッド内の super は自動でそのインスタンスが親クラスのメソッドを呼び出せるオブジェクトを返してくれる
◆ メソッド外でも super に引数渡せば同じことができる
◆ 多重継承すると mro が複雑になるし 親がどれかわかりづらい
◆ 普通に クラス名.メソッド名 に self としてインスタンス渡せば良さそう
◆ メソッド外でも super に引数渡せば同じことができる
◆ 多重継承すると mro が複雑になるし 親がどれかわかりづらい
◆ 普通に クラス名.メソッド名 に self としてインスタンス渡せば良さそう
super
Python でのメソッドのオーバーライドは普通に継承して同じ名前でメソッド定義するだけですclass A:
def foo(self):
return 1
class B(A):
def foo(self):
print("SUPER:", super().foo())
return 2
print(B().foo())
SUPER: 1
2
親のメソッドを呼び出したいときは super().method_name() のようにして呼び出します
self を渡す必要はないです
Python の super は関数で super を呼び出すことで親メソッドを呼び出すためのオブジェクトを取得できます
JavaScript の感覚で super.method_name() のように使ってしまうとエラーです
ついこれやってしまうんですよね
呼び出し場所
super は実行場所がメソッドの中だと引数無しで そのインスタンスの親メソッドへアクセスするためのオブジェクトを取得しますclass A:
def foo(self):
s = super()
print(s)
class B(A):
def foo(self):
s = super()
print(s)
s.foo()
B().foo()
<super: <class 'B'>, <B object>>
<super: <class 'A'>, <B object>>
グローバルや通常の関数内など その他の場所だと引数なしはエラーになります
グローバル部分でも引数でクラスとオブジェクトを指定すると同じものを作れます
class A:
def foo(self):
return 1
class B(A):
def foo(self):
return 2
class C(B):
def foo(self):
return 3
c = C()
print(super(C, c))
print(super(B, c))
print(super(A, c))
print(super(C, c).foo())
print(super(B, c).foo())
print(super(A, c).foo())
<super: <class 'C'>, <C object>>
<super: <class 'B'>, <C object>>
<super: <class 'A'>, <C object>>
2
1
Traceback (most recent call last):
File "main.py", line 21, in <module>
print(super(A, c).foo())
AttributeError: 'super' object has no attribute 'foo'
1 つ目の引数ではクラスオブジェクトを指定します
ここで指定されたクラスの親クラスのメソッドを実行できます
B のメソッドを実行するには C を指定しないといけません
B を指定してしまうと その親の A のメソッドが実行されます
ちょっとわかりづらいのですが関数が 「super」 ということを考えるとこのほうが自然と言えます
mro
Python での親クラスはクラスオブジェクトの __mro__ プロパティで見えますmro は method resolution order の略です
class A: pass
class B1(A): pass
class B2(A): pass
class C1(B1): pass
class C2(B2): pass
print(A.__mro__)
print(B1.__mro__)
print(B2.__mro__)
print(C1.__mro__)
print(C2.__mro__)
(<class '__main__.A'>, <class 'object'>)
(<class '__main__.B1'>, <class '__main__.A'>, <class 'object'>)
(<class '__main__.B2'>, <class '__main__.A'>, <class 'object'>)
(<class '__main__.C1'>, <class '__main__.B1'>, <class '__main__.A'>, <class 'object'>)
(<class '__main__.C2'>, <class '__main__.B2'>, <class '__main__.A'>, <class 'object'>)
多重継承
多重継承をするとこうなりますclass A1: pass
class A2(A1): pass
class B1: pass
class B2(B1): pass
class C(A2, B2): pass
print(A1.__mro__)
print(A2.__mro__)
print(B1.__mro__)
print(B2.__mro__)
print(C.__mro__)
(<class '__main__.A1'>, <class 'object'>)
(<class '__main__.A2'>, <class '__main__.A1'>, <class 'object'>)
(<class '__main__.B1'>, <class 'object'>)
(<class '__main__.B2'>, <class '__main__.B1'>, <class 'object'>)
(<class '__main__.C'>, <class '__main__.A2'>, <class '__main__.A1'>, <class '__main__.B2'>, <class '__main__.B1'>, <class 'object'>)
C を見ると
C > A2 > A1 > B2 > B1 > object
という継承です
A2 側と B2 側で共通の親クラスが object なので A2 側の object の手前までの A2 > A1 があってその次に B2 に行きます
B2 > B1 まで来ると次に共通の object となります
A
|
+-----+
| |
B F
| |
+--+ +--+
| | | |
C D G H
| | | |
+--+ +--+
| |
E I
| |
+-----+
|
J
こういう図の継承にして J の __mro__ を表示してみます
class A: pass
class B(A): pass
class C(B): pass
class D(B): pass
class E(C, D): pass
class F(A): pass
class G(F): pass
class H(F): pass
class I(G, H): pass
class J(E, I): pass
for x in J.__mro__:
print(x)
<class '__main__.J'>
<class '__main__.E'>
<class '__main__.C'>
<class '__main__.D'>
<class '__main__.B'>
<class '__main__.I'>
<class '__main__.G'>
<class '__main__.H'>
<class '__main__.F'>
<class '__main__.A'>
<class 'object'>
多重継承と super
super で見る親クラスは mro の順番で一つ次のものです多重継承するとこういう問題もあります
class A1:
def method(self):
print("A1")
class A2(A1):
def method(self):
print("A2")
super().method()
class B:
def method(self):
print("B")
class C(A2, B):
def method(self):
print("C")
super().method()
C().method()
C
A2
A1
B は mro の順番では A1 の次にあります
A1 で super().method() を実行していないので B の method は呼び出されません
A1 内で super().method() を呼び出すようにしておけば A1 の次に B の method を呼び出せます
しかし A1 が B を継承してるわけではないので多重継承の場合のみ動くというものになってしまいます
そうした場合 A1().method() で実行すると A1 の次は object で object に method というメソッドはないのでエラーです
そういうときに super の引数が使えます
class A1:
def method(self):
print("A1")
class A2(A1):
def method(self):
print("A2")
super().method()
class B:
def method(self):
print("B")
class C(A2, B):
def method(self):
print("C")
super(A1, self).method()
C().method()
C
B
ただ B を実行したいときに B の 1 つ前は A1 ということを知っておかないといけないです
調べればわかるはずですが 少し考える必要がありますし 読むときにも A1 の次は何かを mro を脳内で作って考える必要があり あんまり良い方法に思えません
Python だとインスタンスメソッドも直接 class オブジェクトから参照できて引数に self を入れれば実行できます
なので直接呼び出したほうがわかりやすいと思います
c = C()
B.method(c)
# B
JavaScript で prototype のメソッドを call するようなものです
class A { method() { console.log("A") } }
class B { method() { console.log("B") } }
A.prototype.method.call(new B())
// A