どぶお/Pythonで遊ぼう!

あるデコレータをオブジェクトメソッドに無理矢理対応させる  

何をするか  

あるデコレータを使おうとしたら、オブジェクトメソッドの対応を考慮されていなかった。最悪そのデコレータだけ書き直せばいいのですが、バージョンアップ等に対応するのが面倒だし、あまりいじりたくない。そんな場合に無理矢理オブジェクトメソッドに対応させるデコレータを考えてみました。

つまり、オブジェクトメソッドに対応するには第一引数がオブジェクトの場合を考慮しておかなければなりませんが、そういう処理がされていなかった場合にどう対応するか?というお話しです。

具体的な例  

Djangoを使っていた話。
Django 1.5(おそらくそれ以前も)、のdjango.views.decorators.http.require_http_methodsデコレータ(以降require_http_methods)はリクエストのメソッドを制限するデコレータであり、通常はviewsに適用されます。例えば以下。

@require_http_methods(["POST", "OPTIONS])
def browse_articles(req, *args, **kw):
    ...
    return HttpResponse("Your articles")

Djangoではviewsは関数として実装することになってい(たぶん)ますが、viewsをオブジェクトメソッドとして実装すると以下のようになります。

class MyView(object):
    @require_http_methods(["POST", "OPTIONS"])
    def browse_articles(self, req, *args, **kw):
        ...
        return HttpResonse("Your articles")

urls.pyにはメソッドオブジェクトとして渡せばコールすることは可能ですが、第一引数にselfが入るので、以下の例外を吐きます。

AttributeError: 'MyView' object has no attribute 'method'

なぜならrequire_http_methodsは第一引数はHttpRequestオブジェクト決めうちなので、MyViewオブジェクトが渡されてしまうとAttributeErrorになってしまいます。そこで、require_http_methodsへの引数はselfを除いたもの、しかし元のメソッドにはselfを含めて渡すという、なかなかトリッキーな手法を採ることになります。で、頭がこんがらがりながらも実現したのが以下。

※検索すればわかりやすい解説はいくらでもあるので詳しくは述べませんが、デコレータを通したコールは以下と同様です。

require_http_methods(["POST", "OPTIONS"])(browse_articles)(req)

MyViewの場合は以下。

require_http_methods(["POST", "OPTIONS"])(obj.browse_articles)(req)

の、はず [huh]

無理矢理解決!  

まず、require_http_methodsをデコレートするデコレータを定義。

def enable_object_method(target_decorator):
    def wrapped_decorator(func):
        def executable_decorator(self, *args, **kw):
            def f(*tmp_args, **tmp_kw):
                return func(self, *tmp_args, **tmp_kw)
            return target_decorator(f)(*args, **kw)
        return executable_decorator
    return wrapped_decorator

で、viewsは以下のようにします。

class MyView(object):
    @csrf_exempt
    @enable_object_method(require_http_methods(["GET"]))  <-- ポイント!
    @commit_on_success
    def hello(self, req):
        return HttpResponse("Hello, world!")

これで、無理矢理動かすことができます(^^;。

解説  

デコレータの動作はややこしいので肝心の部分だけを説明します。 executable_decoratorの中でfという関数を定義していますが、これは一種のカリー化であり(用語として正しい?)、元の関数funcに対してselfをあらかじめクロージャに入れてしまい、引数の数を変えています。
これをrequire_http_methodsでラップすることで本来第一引数であるオブジェクト(self)を無視することができるわけです。ここまでするメリットがどの程度かは分かりませんが、まあ、頭の体操と言うことで。
デコレータ用のデコレータっていうところでややこしかったです。

多分普段カリー化とか使っている人には当たり前の手法かも知れませんが、結構悩んだのでメモしておきます。追加解説などあればコメント戴けるとうれしいです(^^)。

※蛇足ですが、なぜこんなことがしたかったかというと、JSON-RPCに対応するためのサービスプロバイダをクラスで作りたかった、ということなんですけどね・・・

コメント