python:decorator

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)

 デコレータによる関数ラップを行うと、関数のドキュメンテーション文字列などが失われてしまう。

# -*- 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)
  • python/decorator.txt
  • 最終更新: 2020/02/25 11:14
  • by ともやん