Pythonで、super()で呼ばれる親メソッドの中で呼ばれるメソッドを、子でオーバーライドしてみた

良いタイトルが思い浮かばなかったのですが...

以下のソースコードを実行した時に、何がprintされるかを試した時のメモです。

class Parent:
    def reply(self):
        self.say()

    def say(self):
        print('parent!')


class Child1(Parent):
    def reply(self):
        super().reply()

    def say(self):
        print('child1!')


if __name__ == '__main__':
    c1 = Child1()
    c1.reply()
    # => ??

 
このソースコードの挙動です。

  • 子の reply() メソッドでは、親の reply() メソッドを呼ぶ
  • 親の reply() メソッドでは、 self.say() メソッドを呼ぶ

気になる点です。

  • 親子どちらの say() メソッドが呼ばれるのか?

 
目次

 

環境

 

Python3.6の場合

前述のコードを py3.py として保存し、実行してみます。

(env36) $ python py3.py 
child1!

子の say() メソッドが呼ばれました。

 
処理順を確認するため、printを仕込んでみます。

class Parent:
    def reply(self):
        print('[parent - reply]{}'.format(type(self)))
        self.say()

    def say(self):
        print('[parent - say  ]{}'.format(type(self)))
        print('parent!')

class Child1(Parent):
    def reply(self):
        print('[child  - reply]{}'.format(type(self)))
        super().reply()

    def say(self):
        print('[child  - say  ]{}'.format(type(self)))
        print('child1!')

if __name__ == '__main__':
    print('--- parent reply --->')
    p = Parent()
    p.reply()
    print('--- child1 reply --->')
    c1 = Child1()
    c1.reply()

 
実行してみます。

$ python py3.py 
--- parent reply --->
[parent - reply]<class '__main__.Parent'>
[parent - say  ]<class '__main__.Parent'>
parent!
--- child1 reply --->
[child  - reply]<class '__main__.Child1'>
[parent - reply]<class '__main__.Child1'>
[child  - say  ]<class '__main__.Child1'>
child1!

super()で呼んだ親クラスの reply() メソッドの引数selfに Child1 クラスのインスタンスが渡されています。

これは、Python3の super().reply()super(Child1, self).reply() と同じであり、後者が引数に self が渡されていることからも分かります。

その結果、Parentクラスで self.say() した時に、Child1クラスの say() メソッドが呼ばれます。

 
もし、super()を使わない場合は、親クラス.メソッド(引数としてselfを渡す) という形式、ここでは Parent.reply(self) とします。

class Child3(Parent):
    def reply(self):
        print('[child  - reply]{}'.format(type(self)))
        Parent.reply(self)

    def say(self):
        print('[child  - say  ]{}'.format(type(self)))
        print('child3!')

 
実行してみると、super()と同じ結果となりました。

[child  - reply]<class '__main__.Child3'>
[parent - reply]<class '__main__.Child3'>
[child  - say  ]<class '__main__.Child3'>
child3!

 

Python2.7の場合

Python3のコードのうち、以下のように差し替えます。

  • print文へと変更
  • 引数なしの super() がないため、 super(Child1, self) を使う

 

class Parent:
    def reply(self):
        print '[parent - reply]{}'.format(type(self))
        self.say()

    def say(self):
        print '[parent - say  ]{}'.format(type(self))
        print 'parent!'

class Child1(Parent):
    def reply(self):
        print '[child  - reply]{}'.format(type(self))
        super(Child1, self).reply()

    def say(self):
        print '[child  - say  ]{}'.format(type(self))
        print 'child1!'

if __name__ == '__main__':
    print('--- parent reply --->')
    p = Parent()
    p.reply()
    print('--- child1 reply --->')
    c1 = Child1()
    c1.reply()

 
実行してみます。

--- parent reply --->
[parent - reply]<type 'instance'>
[parent - say  ]<type 'instance'>
parent!
--- child1 reply --->
[child  - reply]<type 'instance'>
...
TypeError: super() argument 1 must be type, not classobj

エラーとなりました。super()のところで例外が起きてるようです。

原因は、Parentクラスの定義 class Parent: が、Python2の場合 old-style classes になるためです。

 
そのため、Python2でも動作させるためには、new-style classes として、Parentクラスで object を継承します。

class Parent(object):
    def reply(self):
        # あとは同じ

 
実行結果です。  

[child  - reply]<class '__main__.Child1'>
[parent - reply]<class '__main__.Child1'>
[child  - say  ]<class '__main__.Child1'>
child1!

new-style classesのため、 <class '__main__.Child1'> へと出力が変わりました。

 
もしくは、 old-sytle classesでも使える、 Parent.reply(self) にします。

class Child1(Parent):
    def reply(self):
        print '[child  - reply]{}'.format(type(self))
        Parent.reply(self)

 
実行結果です。

child  - reply]<type 'instance'>
[parent - reply]<type 'instance'>
[child  - say  ]<type 'instance'>
child1!

old-style classesのため、 <type 'instance'> と出力されています。

 

ソースコード

GitHubに上げました。
https://github.com/thinkAmi-sandbox/python_misc_samples/tree/master/e.g._call_overrided_method_using_super

 

参考