Subscribed unsubscribe Subscribe Subscribe

SE Can't Code

A Tokyo based Software Engineer. Not System Engineer :(

Tips, decorator.

Python

デコレータってよく使うのだけれど、使おうとするたびにどう書いたっけとググってばかりいるため、今度こそちゃんと覚えようと思う。汎用的に使いたい機能をデコレータ関数として定義し、利用したい関数の前に書いてやれば、なんとなく拡張されていい感じで汎用的に呼び出されるというイメージだった。なので、キャッシュとか処理時間を取ったりする時にまるっと利用していた。そういう意味では結構代表的な使い方というものがあるみたいで、今回はそのうちの引数出力チェックと処理時間チェックのデコレータをそれぞれ実装した。

引数出力チェックデコレータ

#! /usr/bin/python

from itertools import izip

info = {}

def check_args(in_=(), out=(type(None),)):
    def _check_args(function):
        func_name = function.func_name
        info[func_name] = (in_, out)

        def _check_types(elements, types):
            if len(elements) != len(types):
                raise TypeError('argument count is wrong')
            typed = enumerate(izip(elements, types))

            for index, couple in typed:
                arg, of_the_right_type = couple
                if isinstance(arg, of_the_right_type):
                    continue
                raise TypeError('arg #%d should be %s' % (index, of_the_right_type))

        def __check_args(*args):
            checkable_args = args[1:]
            _check_types(checkable_args, in_)

            res = function(*args)

            if not type(res) in (tuple, list):
                checkable_res = (res,)
            else:
                checkable_res = res
            _check_types(checkable_res, out)

            return res
        return __check_args
    return _check_args



class Test(object):
    @check_args((int, int))
    def meth1(self, int1, int2):
        print('received %d and %d' % (int1, int2))

    @check_args((str,), (int,))
    def meth2(self, phrase):
        print('received %s' % phrase)
        return 12


if __name__ == '__main__':
    print(info)
    my = Test()
    my.meth1(1, 2)
    my.meth2('thanks')
    my.meth2(2)

このようにデコレータに引数として、期待する引数や出力の型や数を渡してあげるとデコレータが動いて、期待通りの動きをしているかをチェックしてくれる。こういった汎用的なチェッカーはAssertionを使うよりもよいかもしれない。チェックに引っかかると以下のような例外出力になる。

sotoshigoto@soto:~/workspace/work/samplecode$ python re_decorator.py 
{'meth2': ((<type 'str'>,), (<type 'int'>,)), 'meth1': ((<type 'int'>, <type 'int'>), (<type 'NoneType'>,))}
received 1 and 2
received thanks
Traceback (most recent call last):
  File "re_decorator.py", line 57, in <module>
    my.meth2(2)
  File "re_decorator.py", line 25, in __check_args
    _check_types(checkable_args, in_)
  File "re_decorator.py", line 21, in _check_types
    raise TypeError('arg #%d should be %s' % (index, of_the_right_type))
TypeError: arg #0 should be <type 'str'>


次はよく使うだろう処理時間チェックのデコレータ。

処理時間チェック

#! /usr/bin/python

import time

def time_func(func):
    def decorator(*args):
        start = time.time()
        ret = func()
        print('%s was executed, it took %s sec' % (func.func_name, time.time()-start))
        return ret
    return decorator

@time_func
def test():
    time.sleep(1)

if __name__ == '__main__':
    test()

このような形で、デコレータを関数の頭につけてあげるといい感じで処理時間が出力される。

sotoshigoto@soto:~/workspace/work/samplecode$ python time_decorator.py 
test was executed, it took 1.00110387802 sec

上記の他で代表的なところだと、キャッシュ、プロキシ、コンテキストプロバイダが挙げられる。
あと自分のプロジェクトではdebugモードのlog出力の際に表記を合わせる意味でデコレータを利用していた。たとえば、実行したSQLのクエリをlogに吐くようにして、どんなパラメータをもらっていて実行されているのかといった、解析の際にlogから確認できるようにしていた。プロジェクト共通なモジュールとして、デコレータを実装してしまうというやり方としていいかもしれない。

まだまだ使い道が多々ありそうなので、デコレータがどういった用途で使われているのかを他のコードを見て勉強したいなと思う。

Remove all ads