毎日Learning

学んだことを共有します

エキスパート Python プログラミング メモ Vol.5

2.3.1 デコレータの書き方 の続き…

サブ関数に対して、wrapperのような一般名詞の代わりに _mydecorator のような明示的な名前をつけるのが良いプラクティスです。
その処理過程でエラーが発生したときに、トレースバックが読みやすくなり、デコレータの内部をデバッグすればいい、ということがすぐにわかるからです。

なるほど。そのほうが分かりやすいね。

デコレータに引数が必要なときは、2重にラッピングする必要があります。

はい。

def mydecorator(arg1, arg2):
    def _mydecorator(function):
        def __mydecorator(*args, **kw):
            # 実際の関数を呼び出す前に行う処理
            res = function(*args, **kw)
            # 呼び出し後に行う処理
            return res
        return __mydecorator
    return _mydecorator

デコレータが読み込まれて処理されるのは、インタプリタがモジュールを最初に読み込むタイミングになります。
デコレータはミドルウェアのように使用されて、処理の流れを理解したりデバッグしたりするのが難しくなるので、その使用方法は汎用的なラッパーに制限されるべきです。

ふむふむ。よく読まんと分からん。

つまりなんや。JavaでいうとDIみたいなもんやから処理おっかけにくいよってことかな。あんまこった実装するとおっかけられへんでーってことかな。

もしも、メソッドが定義されている特定クラスや、ある関数の引数とデコレータが密接に関係があり、他のメソッドや関数に適用できないのであれば、それはデコレータにすべきではありません。
通常の関数呼び出しにリファクタリングして、シンプルにしましょう。

ああ。デコレータかっこいいよ。かっこいいよ。って言うて無駄にデコレータにすると、処理の理解がむっこくなるし、やり過ぎ注意ってことね。
あるあるや。

デコレータをAPIとして提供する場合、メンテナンスしやすいように1つのモジュール内にまとめるのが良いプラクティスです。

そうしていただけると助かります。

デコレータはラップした関数やメソッドが受け取る引数や、返り値だけを見るようにして、もし必要だったとしても、その関数の内部を解析するのはできるだけ行わないようにするべきです。

これもやり過ぎ注意ってことかな。以降はデコレータの一般的な使用方法について紹介されてる。

2.3.2 引数チェック

XML-RPCを例に挙げて紹介しているが、割愛してコードだけ書く。

>>> from itertools import izip
>>> rpc_info = {}
>>> def xmlrpc(in_=(), out=(type(None),)):
...     def _xmlrpc(function):
...         # 引数情報の登録
...         func_name = function.func_name
...         rpc_info[func_name] = (in_, out)
...
...         def _check_types(elements, types):
...             """Subfunction that checks the 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 __xmlrpc(*args): # キーワード引数は受けとれない
...             # 入力をチェックする
...             checkable_args = args[1:] # selfを削除する
...             _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 __xmlrpc
...     return _xmlrpc

このサンプルのデコレータは、グローバルな辞書に関数を登録し、その引数と返り値のリストを保持します。
引数チェックデコレータを使用したサンプルが、とてもシンプルになるのが重要なポイントです。

ほいで使用方法は次の通り。

>>> class RPCView(object):
...
...     @xmlrpc((int, int)) # two int -> None
...     def meth1(self, int1, int2):
...         print('received %d and %d' % (int1, int2))
...
...     @xmlrpc((str,), (int,)) # string -> int
...     def meth2(self, phrase):
...         print('received %s' % phrase)
...         return 12
...

だとさ。ほー。便利だー。Pythonのように型を引数に特定しない言語で型チェックを実装するのってめんどいけど、
デコレータを使えばすっきり作れて良いね。

他にもさまざまな使用方法もあるんだってー。