python / django / クラスデコレータでモデルオブジェクトにメソッドを追加する
複数のクラスに共通のメソッドを持たせるときには継承を使うのが普通だが、事務処理的な機能を与えるためにサブタイプを作るなんて大袈裟だと思う。そもそも、継承っていうのは一連の関連した挙動をモデルするためのもので、それを便宜のために使うのは乱用のような気がする。
そう言えばC++だと継承でしか挙動を拡張したり変更したりできないんで、関数を一個はめこむためのクラスを沢山作って、何十という継承を重ねていたコードがあったな。MSのアクティブなんちゃらとか言うやつ。あれはvtableの乱用だ。幸い、ダイナミックな言語には継承以外でもメソッドを追加できる。
こんな風に使いたい。
とすると
@add_m_url
class Annotation(Model):
....
を宣言したようになる。つまり、m_url()というメソッドをクラスに追加してくれるデコレータだ。こうい細かいことはデコレータにさせることにより、継承は変な制約をうけずにもっと重要なことに使える。
def m_url(self):
""" モデルインスタンスの住んでいる場所 """
return '/model/%s/%d/' % ('Annotation', self.id)
デコレータの実装。
「id」というprimary keyがあるというのが前提だが、これがディフォルトなので特に変ったことをしていなければクリアされる条件だ。
def add_m_url(cls):
""" モデルクラスにm_url()メソッドを猿パッチするデコレータ """
setattr(cls,
'm_url',
lambda self: '/model/%s/%d/' % (self.__class__.__name__, self.id))
return cls
これで全てのモデルクラスのオブジェクトに/mode/ClassName/id/のようなURLが備わる。
はたして、このようなデコレータがクラスにメソッドを追加するpythonicなやりかたかどうか知らないけど、これで動いているんで暫くこれでやってみることにしよう。
しかし、このデコレータってのは便利なものだ。極簡単な仕組だがかなりのことができる。LISPのマクロほどのパワーは全然無いが、分りやすく使いやすい。シンプルさとパワーの絶妙なバランスがとれていると思う。こういうトレードオフの巧さがpythonを魅力的なものにしている。