はじめに
Twitterで一時期流行していた 100 Days Of Code なるものを先日知りました。本記事は、初学者である私が100日の学習を通してどの程度成長できるか記録を残すこと、アウトプットすることを目的とします。誤っている点、読みにくい点多々あると思います。ご指摘いただけると幸いです!
今回学習する教材
-
Effective Python
- 8章構成
- 本章216ページ
今日の進捗
- 進行状況:69-73ページ
- 第3章:クラスと継承
- 本日学んだことの中で、よく忘れるところ、知らなかったところを書いていきます。
親クラスをsuperを使って初期化する
子クラスから親クラスを初期化する方法
-
__init__
メソッドを用いた初期化- 問題点
- superを用いた初期化
__init__
メソッドを用いた初期化
# 親クラス
class MyBaseClass(object):
def __init__(self, value):
self.value = value
# 子クラス
class MyChildClass(MyBaseClass):
def __init__(self):
MyBaseClass.__init__(self, 5) # 親クラスの__init__メソッドを呼びだし初期化
__init__
メソッドを用いた初期化の問題点
この方式は、単純な階層では問題なく動作しますが、多重継承によって、影響を受けている状態でスーパークラスの__init__
メソッドを直接呼び出すと、おかしな振る舞いを起こす場合があります。特に、ダイヤモンド継承の際に、予期せぬ振る舞いを起こします。
ダイヤモンド継承とは、サブクラスが2つの別々のクラスから継承し、かつその2つが継承改装で同じスーパークラスを持っていることを指します。例えば、MyBaseClassを継承する2つの子クラスとそれらを継承する子クラスを次のように定義します。
# 親クラス
class MyBaseClass(object):
def __init__(self, value):
self.value = value
# 親クラスを継承する子クラス1
class TimesFive(MyBaseClass):
def __init__(self, value):
MyBaseClass.__init__(self, value)
self.value *= 5
# 親クラスを継承する子クラス2
class PlusTwo(MyBaseClass):
def __init__(self, value):
MyBaseClass.__init__(self, value)
self.value += 2
# 2つのクラスを継承する子クラス定義し、MyBaseClassをダイヤモンドの頂点に
class ThisWay(TimesFive, PlusTwo):
def __init__(self, value):
TimesFive.__init__(self, value)
PlusTwo.__init__(self, value)
foo = ThisWay(5)
print('Should be ( 5 * 5) + 2 = 27 but is', foo.value)
出力結果
Should be ( 5 * 5) + 2 = 27 but is 7
出力は、ThisWayの引数5がTimesFiveで5倍され、PlusTwoで2を加算し、27になるはずですが、7になっています。この原因は、PlusTwo.__init__の呼び出しで、MyBaseClass.__init__が2回目に呼び出されたところで5にリセットされるためです。Python3ではsuperを使うことでこの問題を解決することができます。また、Python3では常にsuperを使うべきです。
superを用いた初期化
class Explicit(MyBaseClass):
def __init__(self, value):
super(__class__, self).__init__(value * 2)
class Implicit(MyBaseClass):
def __init__(self, value):
super().__init__(value * 2)
print('Explicit', Explicit(10).value)
print('Implicit', Implicit(10).value)
出力結果
Explicit 20
Implicit 20
まとめ
- Pythonの標準メソッド解決順序は、スーパークラスの初期化順序とダイヤモンド継承の問題を解決する
- 親クラスを初期化するには、常に組み込み関数superを使う