サブクラスに特定の処理を強制したい
Pythonでとあるクラスを継承したサブクラスを設計するとき、継承先には必ずこんな処理をして欲しい...なんてことがあります。
例えば__init__
が呼ばれた時に必ずsuper().init()を呼んで欲しい...などの場合です。
__init__
に限らず「親クラスのメソッドをオーバーライドしたいけど、親クラスのメソッドもそのまま呼び出したい!」なんて処理はよくよく現れるものですが、親クラスのメソッドを呼び出す処理を書き忘れてしまってエラーを吐かせるのはいやですし、そもそも親クラスのメソッドを呼び出す仕様そのものが面倒くさかったりしますね。
今回は子クラスのメソッドf
が呼ばれるとき、その処理の後で必ず親クラスのメソッドf
が呼ばれるような実装を実現してみます。
やり方
「子クラスの関数f」を「子クラスの関数fと親クラスの関数fを実行する関数new_f」に書き換える
ってことをやってくれるメタクラスをかく
メタクラスってなんや?って話はまた今度違うところでします。とりあえずはクラスの定義のされ方をいじるクラスだと思っておいてください。
よくわからない人でも下記コードをコピーペーストしてdecorate
メソッドを書き換えるだけでなんでもできると信じています。
前提
Python: 3.7.2 (Python3なら多分OK、Python2でもmetaclassの指定方法が違うだけなはず)
コード
class ParentCaller(type):
def __new__(cls, name, base, attr):
# ParentクラスのfがParentクラスのfを呼ぶのを防ぐ
if name != "Parent":
# クラス生成時、関数fは自動的にデコレートされる仕様にする
attr["f"] = cls.decorate(attr["f"])
return super().__new__(cls, name, base, attr)
@classmethod
def decorate(cls, f):
# 関数fを受け取ると、fとParent.fをセットで実行するnew_fを返す
def new_f(self, x):
f(self, x)
Parent.f(self, x)
return new_f
class Parent(metaclass=ParentCaller):
def f(self, x):
print(f"parent's f says: {x}")
class Child(Parent):
def f(self, x):
print(f"child's f says: {x}")
child = Child()
child.f("Hello World!")
# child's f says: Hello World!
# parent's f says: Hello World!
まとめ
Tensorflowのように人に継承して使ってもらうライブラリを書くときにはなるべく「ここでhogehogeを実行してね!」っていうお約束を避けたいものです。そんな時はメタクラスを書いて相手の知らないところで勝手にhogehogeを呼び出してしまいましょう。
集団での開発ではメタプログラミングって読みにくくて嫌われちゃうらしいですけどね...。個人で開発する分には工数を最小にしてくれるエッセンスなのではないかと思っています。