r0w0

PythonやDeepLearning関連で学んだこと、調べたことの備忘録

PyTorch: Datasetの継承

疑問

PyTorchのDatasetを継承したクラスを作成するときに、super().initを呼び出す必要があるのか、という疑問。そもそも継承時の動作を忘れているのでその思い出しも含めて確認してみる。

コードはこちらの記事を参考に書いています。

継承 in python

結論としては、

  • 子クラスの引数に親クラスを指定することで継承が行われる
  • 継承により、親クラスの変数とメソッドが引き継がれる
  • 子クラスにおける同名の変数/メソッドの作成により上書きがされる となる。

それぞれの動作を確認していきたい。

継承により親クラスの変数とメソッドが引き継がれる

クラス宣言時に親クラスを指定することで行われる。

# 親クラス
class Fish(object):
species = "魚"
def __init__(self, name, build="骨"):
self.name = name
self.build = build

def greeting(self):
print(f"{self.name} in 親クラス です。")

この親クラスに対してコンストラクタ未実装の子クラスを実装、greetingメソッドを実行してみる。 結果、継承により親クラスのコンストラクタが呼び出されるが、引数が足りていないのでエラー。

# 子クラス
class Medaka(Fish):
    def detail_greeting(self):
        print(f"{self.name}です。{Medaka.species}の一種です。{self.build}で構成されています。")

# インスタンス作成とメソッドの実行
medaka = Medaka()

# ---------------------------------------------------------------------------
# TypeError                                 Traceback (most recent call last)
# <ipython-input-50-7a6e90b86513# in <module#
# ----# 1 medaka = Medaka()
# 
# TypeError: __init__() missing 1 required positional argument: 'name'

次に、ここで引数を与えて再実行する。 結果、初期化が成功しメソッドも実効できる。 親クラスのインスタンス変数である self.name, クラス変数である Medaka.species, そしてインスタンスメソッドであるgreetingが参照できていることが分かる。

medaka = Medaka("めだか")
medaka.greeting()
medaka.detail_greeting()

# めだか in 親クラス です。
# めだかです。魚の一種です。骨で構成されています。

子クラスにおける同名の変数/メソッドの作成により上書きがされる

次に、子クラスのコンストラクタを実装して、親クラスの継承コンストラクタをOverideする。

class Medaka(Fish):
    def __init__(self, name):
        self.name = name
        self.dob = "2019年12月12日"
        
    def detail_greeting(self):
        print(f"{self.name}です。{Medaka.species}の一種です。{self.build}で構成されています。{self.dob}生まれです。")

コンストラクタのOverideにより、子クラス内のインストラクタのみが呼び出される。 子クラスのインストラクタでは build 変数を指定していないので子クラスのインスタンスに保持されない。 結果、detail_greetingがエラーとなる。

medaka = Medaka("めだか")
medaka.greeting()
medaka.detail_greeting()
# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# <ipython-input-57-1be94e797675> in <module>
#       1 medaka = Medaka("めだか")
#       2 medaka.greeting()
# ----> 3 medaka.detail_greeting()
# 
# <ipython-input-56-d547077f6b07> in detail_greeting(self)
#       5 
#       6     def detail_greeting(self):
# ----> 7         print(f"{self.name}です。{Medaka.species}の一種です。{self.build}で構成されています。{self.dob}生まれです。")
# 
# AttributeError: 'Medaka' object has no attribute 'build'

子クラス内で親クラスのコンストラクタをOverideしたうえで、親クラスのインスタンス変数も利用したい。 その場合、super().__init__(name) により親クラスのコンストラクタを、子クラスのコンストラクタから呼び出すことになる。

class Medaka(Fish):
    def __init__(self, name):
        super().__init__(name)
        self.dob = "2019年12月12日"
        
    def detail_greeting(self):
        print(f"{self.name}です。{Medaka.species}の一種です。{self.build}で構成されています。{self.dob}生まれです。")

結果として、親クラスのコンストラクタ内で宣言している self.name や self.build が作成される。

medaka = Medaka("めだか")
medaka.greeting()
medaka.detail_greeting()

# めだか in 親クラス です。
# めだかです。魚の一種です。骨で構成されています。2019年12月12日生まれです。

 継承 in TORCH.UTILS.DATA.DATASET

ソースから以下のことが分かる。

  • objectを継承
  • コンストラクタ及びインスタンス変数が存在しない
  • getitemaddのメソッドのみ実装されている
class Dataset(object):

    def __getitem__(self, index):
        raise NotImplementedError

    def __add__(self, other):
        return ConcatDataset([self, other])

  情報と結論をまとめると、

  • super().__init__() を実行すると、Dataset内で init が実装されていないので、 object の init が呼ばれる
  • object の init を呼ぶことに特別な意味があるとは今のところ聞いていない
  • 従って、自作Datasetのコンストラクタ内で super().__init__() を呼ぶ意味は無い

Datasetを継承し、子クラス内でコンストラクタや各メソッドをOverideすればOKと考えられる。