はじめに
Pythonでクラス継承をどう書くのか?について実践編を書きます。
なお基礎編はありません。
いつ重要?
ソフトウェア、大規模ライブラリを作る際に有用と思われます。
複数クラスで同じように定義するメソッドを、抽象クラスや鋳型となる親クラスに書いておけば子クラスで定義する必要がなくなります。
今回はみんな大好き scikit-learn のTransformerMixin
を例に見ていきます。TransformerMixin
とは、scikit-learnの随所で利用されているクラスです。scikit-learnでは前処理などでfit
とtransform
を使うことが多く、それらをまとめて行うfit_transform
がTransformerMixin
に実装されています。
この機構により、を継承するだけでfit_transform
が利用できるようになる、という非常にシンプルな例です。
参考までに、通常pythonではabc.ABCMeta
を継承した抽象クラスの実装が行われます。Pythonで常備されているパッケージにabcというものがあり、抽象クラスの定義に利用されます。本稿末尾の参考文献もご確認ください。
試したこと
以下の2点について試しました。書いていきます。
- ソースコードリーディング
- サンプルコード動かした
ソースコードリーディング
まず以下のコードを読んでみましょう。scikit-learnのgithubのページから引用しています。 scikit-learn TransformerMixin
...
class TransformerMixin:
def fit_transform(self, X, y=None, **fit_params):
...
if y is None:
...
return self.fit(X, **fit_params).transform(X)
else:
...
return self.fit(X, y, **fit_params).transform(X)
...
Pythonで抽象クラスを定義する場合よく使われるのはabc.ABCMeta
というクラスです。
しかし、上の例では、abc.ABCMeta
を使うこと無く、メソッドだけ定義されています。
abc.ABCMeta
と@abc.abstractmethod
を利用した抽象クラスでは、継承した先で実装を強要するクラスができます。
対して上記のTransformerMixinでは、共通のメソッドを全ての継承先で利用可能にしたいだけです。この場合は、abc
は利用しなくて良いということです。
サンプルコード動かした
試し書きしてみます。abc
を使った抽象クラスを利用すると、以下の様に動作します。なお、関数名等はアベコベですので気にしないでください...
$ cat sampleAbsclass.py
from abc import ABCMeta, abstractmethod
class DensityMixin(metaclass=ABCMeta):
@abstractmethod
def score(self, X, y=None):
pass
class OneClassSvm(DensityMixin):
def __init__(self):
print('echo echo', 'hello hello')
# def score(self, X, y=None):
# print(*X.shape)
def main():
ocsvm = OneClassSvm()
from numpy.random import randn
X=randn(100, 10)
ocsvm.score(X)
return 0
if __name__=='__main__':
main()
$ python sampleAbsclass.py
Traceback (most recent call last):
File "sampleAbsclass.py", line 21, in <module>
main()
File "sampleAbsclass.py", line 14, in main
ocsvm = OneClassSvm()
TypeError: Can't instantiate abstract class OneClassSvm with abstract methods score
抽象クラスのDensityMixin
内に@abstractmethod
デコレーター付きのメソッドscore
があるのに、それを継承先のOneClassSvm
で実装していないのは**ケシカラン!**というエラーメッセージです。
OneClassSvm
のscore関数のコメントアウトを外せば下記のようにちゃんと動作します。
$ python sampleAbsclass.py
echo echo hello hello
100 10
その他: abc.ABCMetaを使うときの注意点
他に、abc.ABCMeta
を多用すると出てくる注意点として、クラスAが複数の抽象クラスを継承したとき、
「クラスAが1つでもabc.ABCMeta
を継承した抽象クラスを有していれば、他の継承クラスもabc.ABCMeta
を継承していないといけない」
という点があります。これを破ると以下のエラーメッセージが出ます。
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
これは他の言語でも同じ現象が起きるので、そうすることでの利便性があるのだと思います。プログラミング言語の理論をちゃんと学ぶべきですね...
その他2 abc.ABCMetaを使う例
scikit-learnで、abc.ABCMeta
を利用している実装がありますのでそちらも見てみましょう。以下、_PLS - scikit-learnより引用します。__init__
メソッドだけに@abstractmethod
が追加されているのが分かります。
from abc import ABCMeta, abstractmethod
...
class _PLS(BaseEstimator, TransformerMixin, RegressorMixin, MultiOutputMixin,
metaclass=ABCMeta):
...
@abstractmethod
def __init__(self, n_components=2, scale=True, deflation_mode="regression",
mode="A", algorithm="nipals", norm_y_weights=False,
max_iter=500, tol=1e-06, copy=True):
...
def fit(self, X, Y):
...
def transform(self, X, Y=None, copy=True):
...
def predict(self, X, copy=True):
...
def fit_transform(self, X, y=None):
...
def _more_tags(self):
return {'poor_score': True}
この_PLSクラスを他のクラスが継承するのですが、このときだけABCMetaが出てきています。_PLSクラス自体はMixinばかりを継承しており、1番目の例から、ABCMetaクラスを継承したものは持ちません。
なので、abc.ABCMeta
の用途としては繰り返しになりますが複数クラスで必ず実装しないといけないメソッドがある場合に、抽象クラス内で継承するというものが挙げられるでしょう。
(TransformerMixin
を継承しているのに_PLS
内でfit_transform
を再定義しているのは謎です。)
結論
本記事ではscikit-learnのTransformerMixin
の実装例を出発点として、共通部分をまとめるためのクラスをどの様に作るのか、というのを簡単にまとめました。
abc.ABCMeta
クラスを継承して抽象クラスとする、というのは常套手段ですが、単に1, 2個のメソッドを複数クラスに共通して持たせたい場合はabc.ABCMeta
を使うまでもなさそうであることを確認しました。
同じ実装を何度も繰り返してしまうのは愚かなので、うまくクラス継承を利用しましょう。
参考文献
abc.ABCMeta
を使う方法は以下の記事に紹介されています。