====== 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:
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
\\
実行結果:
\\
デコレータ式は、デコレータの第一引数に関数を指定して、関数ラッパーを生成するコードと等価である。
@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:
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:
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)
===== 参考文献 =====
|

|
|
[[http://www.python.jp/doc/2.6/library/functools.html|10.8. functools — 高階関数と呼び出し可能オブジェクトの操作 — Python v2.6.2 documentation]]