【Python】デコレータの基本的な使い方
の基本的な使い方.jpg)
Pythonのデコレータの基本的な使い方について解説します。
デコレータ(decorator)
デコレータとは、関数に機能を追加するための仕組みです。例えば、関数の実行時間を計測したり、ログを出力したりするなど、関数に依存しない汎用的な機能を簡単に付加できます。「飾る・装飾する」という意味から、デコレータは関数を装飾して機能を追加するものと理解してください。
この記事では、Pythonのデコレータの基本的な使い方について紹介します。また、理解を深めるために高階関数やクロージャについても説明します。
高階関数とクロージャ
デコレータは「高階関数での機能の拡張をシンプルに表現する記法」とも言えます。理解を助けるために、高階関数とクロージャについて説明します。
- 高階関数:関数を引数や戻り値として扱う関数
- クロージャ:上位のスコープの変数を保持する関数
以下の例では、高階関数を用いて、sum_range_value関数に「実行時間を計測する機能」を追加しています。
import time
def measure_execution_time(func):
def wrapper(*args, **kwargs):
# 処理前の時刻を設定
timer_start = time.time()
# 対象関数の実行
result = func(*args, **kwargs)
# 処理後の時刻を設定
timer_end = time.time()
# 処理時間を計算
elapsed_time = timer_end - timer_start
print(f"処理実行時間: {elapsed_time} sec")
return result
return wrapper
def sum_range_value(start, end, step=1):
result = 0
for i in range(start, end + 1, step):
result += i
return result
def main():
measure_func = measure_execution_time(sum_range_value)
sum_value = measure_func(1, 1000000)
print(sum_value)
if __name__ == "__main__":
main()【実行結果例】 処理実行時間: 0.044879913330078125 sec 500000500000
measure_execution_time関数は、関数を引数として受け取り、wrapper関数を返却します。このように、関数自体を引数や戻り値に持つ関数を高階関数と言います。
また、wrapper関数は、measure_execution_timeの引数であるfuncを保持したまま返されるため、これをクロージャと呼びます。
呼び出しの流れでは、measure_execution_time(sum_range_value)によりsum_range_valueがfuncに渡され、wrapperにセットされた状態でmeasure_funcに返されます。measure_funcは、wrapper関数であり、その内部でfunc(*args, **kwargs)として元のsum_range_valueが実行されます。
このように、高階関数によって関数の機能拡張を行うことができます。
デコレータの使い方
高階関数による機能拡張は、デコレータを使うことで簡単に実現できます。
import time
from functools import wraps
def measure_execution_time(func):
"""時間計測デコレータ関数
Args:
func: 対象関数
Returns:
wrapper関数
"""
@wraps(func)
def wrapper(*args, **kwargs):
"""内部ラッパー関数"""
# 処理前の時刻を設定
timer_start = time.time()
# 対象関数の実行
result = func(*args, **kwargs)
# 処理後の時刻を設定
timer_end = time.time()
# 処理時間を計算
elapsed_time = timer_end - timer_start
print(f"処理実行時間: {elapsed_time} sec")
return result
return wrapper
@measure_execution_time
def sum_range_value(start, end, step=1):
"""範囲内の数値を合計する関数
Args:
start: 開始数値
end: 終了数値
step: ステップ値
Returns:
合計結果
"""
result = 0
for i in range(start, end + 1, step):
result += i
return result
def main():
sum_value = sum_range_value(1, 1000000)
print(sum_value)
print(f"\ndocstring:\n{sum_range_value.__doc__}")
if __name__ == "__main__":
main()【実行結果例】
処理実行時間: 0.03454852104187012 sec
500000500000
docstring:
範囲内の数値を合計する関数
Args:
start: 開始数値
end: 終了数値
step: ステップ値
Returns:
合計結果デコレータは関数定義の上に「@デコレータ名」と記述します。例えば@measure_execution_timeとすることで、sum_range_value関数に実行時間の計測機能が付加されます。
このようにデコレータを使うことで関数の機能拡張が簡単に行えます。
デコレータ使用時の注意事項(メタ情報の保持)
デコレータを使用する際、オリジナル関数のメタ情報(docstringなど)が失われることがあります。この問題は、functoolsの@wrapsデコレータを使うことで解決できます。
@wraps(func) def wrapper(*args, **kwargs):
@wrapsデコレータを使用しない場合、メタ情報が以下のようにwrapper関数のものになってしまいますので注意が必要です。
【実行結果】 内部ラッパー関数
上記については「エキスパートPythonプログラミング」という書籍に記載があります。Python応用編として色々な知見が得られますので参考にしていただければと思います。
複数のデコレータを重ねて使用する
デコレータは、複数重ねて使用ができます。以下は、measure_execution_timeに加えて、関数名と引数情報を表示するshow_function_infoデコレータを追加した例です。
import time
from functools import wraps
def measure_execution_time(func):
"""時間計測デコレータ関数
Args:
func: 対象関数
Returns:
wrapper関数
"""
@wraps(func)
def wrapper(*args, **kwargs):
"""内部ラッパー関数"""
# 処理前の時刻を設定
timer_start = time.time()
# 対象関数の実行
result = func(*args, **kwargs)
# 処理後の時刻を設定
timer_end = time.time()
# 処理時間を計算
elapsed_time = timer_end - timer_start
print(f"処理実行時間: {elapsed_time} sec")
return result
return wrapper
def show_function_info(func):
"""関数情報表示用デコレータ関数
Args:
func: 対象関数
Returns:
wrapper関数
"""
@wraps(func)
def wrapper(*args, **kwargs):
"""内部ラッパー関数"""
print("--------------------------------------------------------------")
print(f"Name: {func.__name__}")
print(f"args: {args}")
print(f"kwargs: {kwargs}")
print("--------------------------------------------------------------")
return func(*args, **kwargs)
return wrapper
@measure_execution_time
@show_function_info
def sum_range_value(start, end, step=1):
result = 0
for i in range(start, end + 1, step):
result += i
return result
def main():
sum_val = sum_range_value(1, 1000000)
print(sum_val)
if __name__ == "__main__":
main()【実行結果例】
--------------------------------------------------------------
Name: sum_range_value
args: (1, 1000000)
kwargs: {}
--------------------------------------------------------------
処理実行時間: 0.044873714447021484 sec
500000500000デコレータを複数適用する場合、上から順に適用され数学の合成関数のようにmeasure_execution_time(show_func_info(sum_range_value))となります。
このように、複数のデコレータを重ねることで関数の拡張が簡単に表現できます。
まとめ
Pythonのデコレータの基本的な使い方について解説しました。また、デコレータの理解の助けとなる高階関数とクロージャに関する考え方も紹介しています。
デコレータは「@デコレータ名」という表記を関数に付与するだけで簡単に関数に機能を付与することが可能です。ただし、functoolsの@wrapsデコレータを使用しない場合、docstringなどのメタ情報が内部関数のものになるので注意してください。
デコレータは、関数に対して汎用的な機能拡張を行う際に非常に便利な方法です。ぜひ使い方を覚えて活用してみてください。
上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。


内包表記の使い方.jpg)
関数-_yieldによる返却_.jpg)
.jpg)


の使い方.jpg)