📘

Pythonでclassを使う: 設計編(2) 汎用親クラスの設計

2022/12/29に公開

結論

from abc import ABCMeta, abstractmethod

class BaseModel(metaclass=ABCMeta):
    __slots__ = ('name')
    @abstractmethod
    def calc(self):
        pass
    
class Model(BaseModel):
    __slots__ = ('name')
    def calc(self):
        #具体的な処理を記述
	pass 

はじめに

似たようなclassを複数作成する場合の汎用的な親クラスの作成方法について述べます。

内容

汎化用のclassの要件

汎化用のclassに求める要件は以下です。

  1. 用いる共通メソッドを指定できる
  2. 用いるインスタンス変数を指定できる(もしくは用いるインスタンス変数を指定できる環境を提供する)

1については、汎用的なclassに必要な最低限のメソッドを定義できるので、親クラスさえ見ればそのクラスで何をやっているかわかるのでプログラムの可読性が上がる。

また以下のような関数で、(型ヒントを用いて)オブジェクトのメソッドを呼ぶ際にも(共通のメソッドがあるとわかっているので)役立ちます。

def call_model_calc(model:BaseModel):
    model.calc()

abc

abcを使う目的は、用いる共通メソッドを指定できることです。

使用方法は以下です。

metaclassでABCMetaを継承します。
メソッドは、abstractmethodでデコレートします。

from abc import ABCMeta, abstractmethod

class BaseModel(metaclass=ABCMeta):
    @abstractmethod
    def calc(self):
        pass

似たようなことはtyping.Protocolでもできますが、意図が異なるので今回はabcを用います。

詳細については[2]の参考記事がわかりやすいです。

BaseModelを継承したクラスは、BaseModelabstractmethodを定義していないとTypeErrorが返されます。

class WrongModel(BaseModel):
    pass
In : w = WrongModel()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-51ac0255938f> in <module>
----> 1 w = WrongModel()

TypeError: Can't instantiate abstract class WrongModel with abstract methods calc

__slots__

__slots__を使う目的は、用いるインスタンス変数を指定できる(もしくは用いるインスタンス変数を指定できる環境を提供する) ことです。

__slots__で定義した変数以外のインスタンス変数は使えなくなるので、インスタンス変数を制限したいときに役立ちます。(また使用メモリも節約できます)

実行例は以下で、__slots__で定義していない変数を使うとAttributeError が返されます。

In : m = Model()
In : m.name = 'name' #errorが出ない
In : m.hoge = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-5f285424b34a> in <module>
----> 1 m.hoge = 1

AttributeError: 'Model' object has no attribute 'hoge'

親クラスで定義した__slots__は子クラスで上書きできます。

このとき親クラスで__slots__を定義しておかないと、子クラスで__slots__を使うことができない仕様です[1]。(なので親クラスで__slots__を使用する目的に括弧書きで用いるインスタンス変数を指定できる環境を提供すると記載しました。)

具体例

例えば機械学習モデルの親クラスと子クラスは以下のように定義すればよいです。

from abc import ABCMeta, abstractmethod

class BaseMLModel(metaclass=ABCMeta):
    __slots__ = ('parameters')
    
    def __init__(self, parmeters):
        self.paramters = parameters
    # basefunc
    def get_parameters(self):
	return self.parameters
    
    @abstractmethod
    def forward(self):
        pass
    
class Model(BaseMLModel):
    __slots__ = ('parameters')
    def forward(self):
        #具体的な処理を記述
	pass 
	

過去記事

https://zenn.dev/sergicalsix/articles/2c7832353b6afa

参考

[1] __slots__について

https://docs.python.org/3/reference/datamodel.html#slots

[2] abcProtocolについて

https://zenn.dev/ibaraki/articles/bef0b43522475b

Discussion