状態を持ったmixinを作りたいときの検証用のmixin

状態を持ったmixinを作りたいときがある。(self.fooに依存しているけれど、self.fooが定義されているかわからない)
困ったので__init__後に、self.fooが定義されているか調べるデコレータを書いてみた。

毎回検証処理が走っても無駄なだけなので、成功したら元の__init__に上書きされる。

こんな感じで使う。

@init_validate("x", "y")
class Foo(object):
    def __init__(self):
        self.x = 10

self.x,self.yが利用できることを検証している。

Foo() #self.yが存在しないのでエラー

残念ながら、__init__後に追加されるself.fooについては検証できない。

コード

# -*- coding:utf-8 -*- 
def _need_check(title, target, needs):
    for k in needs:
        if not hasattr(target, k):
            raise NotImplementedError("%s: %s is not found" % (title, k))

def init_validate(*needs):
    """ 初回に__init__を呼んだ時に、指定したself.fooを持っているか調べる
    """
    def _init_validate(klass):
        __init__ = getattr(klass, "__init__")

        def do_when_init(init_function):
            def _do_when_init(self, *args, **kw):
                init_function(self, *args, **kw)
                _need_check("instance val", self, needs)
                setattr(klass, "__init__", init_function)

            return _do_when_init
        setattr(klass, "__init__", do_when_init(__init__))
        return klass
    return _init_validate

def class_validate(*needs):
    """ こっちはクラス変数を見る
    """
    def _class_validate(klass):
        _need_check("class val", klass, needs)
        return klass
    return _class_validate

Mixinで使いたい

元々、これを使いたいのはmixinを作るときだった。
mixin先のクラスでvalidateしたいself.fooを記述するより、mixinを定義した時に作った方が良い。

そういう風なmixinを作る。

class ValidateMixin(object):
    """ mixinクラスを渡した時、そのクラスのneedsというクラス変数に格納されているself.fooを調べる。
        というvalidateを持ったMixin
    """

    @classmethod
    def validate(cls, klass):
        klass = class_validate("needs")(klass)
        return init_validate(*cls.needs)(klass)

継承したmixinクラスにはcls.needsが必要。これを使ってmixinを利用したクラスのself.fooを調べる感じ。

ValidateMixinの使い方
class GreetingMixin(ValidateMixin):
    needs = ["name"] # mixinしたクラスではself.nameが使えること!!

    def greet(self):
        print "%s: hello" % self.name

@GreetingMixin.validate # self.nameある?
class Me(GreetingMixin):
    pass

Me() # self.nameないので失敗する。
# NotImplementedError: instance val: name is not found

そんな感じ。