文書の過去の版を表示しています。
Python デコレータ
Python のデコレータは、関数やメソッドの機能を拡張することができる。
デコレータの基本形
デコレータは関数やメソッドをラップすることにより、ラップした処理の実行の有無やタイミングを制御することができる。そして、ラップした処理に対する引数や戻り値を取得して、処理の前後に独自の追加機能を実行することが可能となる。
以下に add 関数を @func_info デコレータでラップする例を示す。
# -*- coding: utf-8 -*- from functools import wraps # python 2.5 以降で利用可能 # 関数情報を表示するデコレータ def func_info(func): # 引数 func をラップする関数を定義 @wraps(func) def _func_info(*args, **kwargs): print '--- func_info ---' print 'func:', func print 'args:', args, 'kwargs:', kwargs # 引数 func を実行 result = func(*args, **kwargs) print 'result:', result print '-----------------' # 引数 func の実行結果を返却 return result # 引数 func をラップした関数を返却 return _func_info # デコレートされた関数を定義 @func_info def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2 # デコレートされた関数を実行 print '関数実行->: add(1, 2)' print add(1, 2)
実行結果:
関数実行->: add(1, 2) --- func_info --- func: <function add at 0x1644230> args: (1, 2) kwargs: {} result: 3 ----------------- 3
デコレータの動作原理
関数ラッパーの生成
デコレータとは関数ラッパーを生成する関数であるということが言える。これらはクロージャと呼ばれる仕組みにより実現される。
※クロージャとは定義された環境への参照を持った関数のこと。
# 関数情報を表示するデコレータ def func_info(func): # 引数 func をラップする関数を定義 def _func_info(*args, **kwargs): print '--- func_info ---' print 'func:', func print 'args:', args, 'kwargs:', kwargs # 引数 func を実行 result = func(*args, **kwargs) print 'result:', result print '-----------------' # 引数 func の実行結果を返却 return result # 引数 func をラップした関数を返却 return _func_info
上記のデコレータでは、引数の func 関数をラップした _func_info 関数を返却している。
関数のデコレート
デコレータ式で add 関数をデコレートすると、add 関数は @func_info デコレータの _func_info 関数ラッパーを返すようになる。
@func_info def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2 # add はデコレータの関数ラッパーを返すようになる print add
実行結果:
<function _func_info at 0x22f02a8>
デコレータ式は、デコレータの第一引数に関数を指定して、関数ラッパーを生成するコードと等価である。
@func_info def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2
上記と以下のコードは等価である。
def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2 add = func_info(add)
デコレートされた関数の実行
デコレートされた関数を実行するということは、デコレータの _func_info 関数ラッパーに、ラップされる add 関数の引数を渡して実行しているのと同じことである。
@func_info def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2 print add(1, 2)
実行結果:
--- func_info --- func: <function add at 0x22f0230> args: (1, 2) kwargs: {} result: 3 ----------------- 3
デコレータを関数として実行する場合、上記と等価なコードは以下のようになる。
# func_info(add) は _func_info を返すので # 以下は _func_info(1, 2) を実行しているのと等価 print func_info(add)(1, 2)
ドキュメンテーション文字列が失われないようにする(functools.wraps の使い方)
デコレータによる関数ラップを行うと、関数のドキュメンテーション文字列などが失われてしまう。
# -*- coding: utf-8 -*- # 関数情報を表示するデコレータ def func_info(func): # 引数 func をラップする関数を定義 def _func_info(*args, **kwargs): print '--- func_info ---' print 'func:', func print 'args:', args, 'kwargs:', kwargs # 引数 func を実行 result = func(*args, **kwargs) print 'result:', result print '-----------------' # 引数 func の実行結果を返却 return result # 引数 func をラップした関数を返却 return _func_info @func_info def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2 print '関数名:', add.__name__ print '説明:', add.__doc__
実行結果:
関数名: _wrapper_func 説明: None
この問題を解決するには、デコレータ内部の _func_info の定義に @wraps を追加する必要がある。
# -*- coding: utf-8 -*- from functools import wraps # python 2.5 以降で利用可能 # 関数情報を表示するデコレータ def func_info(func): # 引数 func をラップする関数を定義 @wraps(func) def _func_info(*args, **kwargs): print '--- func_info ---' print 'func:', func print 'args:', args, 'kwargs:', kwargs # 引数 func を実行 result = func(*args, **kwargs) print 'result:', result print '-----------------' # 引数 func の実行結果を返却 return result # 引数 func をラップした関数を返却 return _func_info @func_info def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2 print '関数名:', add.__name__ print '説明:', add.__doc__
実行結果:
関数名: add 説明: パラメータ1、2を加算した値を返します。
引数を必要とするデコレータ
デコレータが引数を必要とする場合は、関数を二重にラップする必要がある。(ここでは inc_doc 引数を追加してみる。)
# -*- coding: utf-8 -*- from functools import wraps # python 2.5 以降で利用可能 # 関数情報を表示するデコレータ def func_info(inc_doc): def _func_info(func): # 引数 func をラップする関数を定義 @wraps(func) def __func_info(*func_args, **func_kwargs): print '--- func_info ---' print 'func:', func if inc_doc: print 'doc:', func.__doc__ print 'args:', func_args, 'kwargs:', func_kwargs # 引数 func を実行 result = func(*func_args, **func_kwargs) print 'result:', result print '-----------------' # 引数 func の実行結果を返却 return result # 引数 func をラップした関数を返却 return __func_info # 引数 func をラップした関数を返却 return _func_info
デコレータの引数(inc_docにTrue)を指定してデコレートする。
# デコレートされた関数を定義 @func_info(True) def add(param1, param2): u"""パラメータ1、2を加算した値を返します。""" return param1 + param2 # デコレートされた関数を実行 print '関数実行->: add(1, 2)' print add(1, 2)
実行結果:
関数実行->: add(1, 2) --- func_info --- func: <function add at 0x21ad500> doc: パラメータ1、2を加算した値を返します。 args: (1, 2) kwargs: {} result: 3 ----------------- 3
デコレータを関数として実行する場合、上記と等価なコードは以下のようになる。
# func_info(True) は _func_info を返すので # func_info(True)(add) は _func_info(add) と等価 # _func_info(add) は __func_info を返すので # 以下は __func_info(1, 2) を実行しているのと等価 print func_info(True)(add)(1, 2)