Pythonの**デコレーター(Decorator)**は、既存の関数の中身を直接変更することなく、その関数の実行前後に特定の処理を追加するための機能です。
ログの出力、実行時間の計測、アクセス権限のチェックなど、複数の関数で共通して行いたい処理(横断的関心事)を実装する際に非常に役立ちます。
この記事では、デコレーターの仕組みから、@ 構文を使った実装方法、そして引数や戻り値を持つ関数に対応させるための実践的な書き方までを解説します。
デコレーターの基本概念
デコレーターの実体は、「関数を引数として受け取り、新しい関数(元の関数に処理を追加した関数)を返す高階関数」です。
まずは、@ 構文を使わずにデコレーターの仕組みをコードで確認します。
# 1. デコレーター関数
def simple_logger(func):
"""
受け取った関数の実行前後にログ出力を追加する関数
"""
# 2. 内部で新しい関数(ラッパー関数)を定義する
def wrapper():
print("--- 処理開始 ---")
func() # 元の関数を実行
print("--- 処理終了 ---")
# 3. 新しい関数を返す
return wrapper
# デコレーション対象の関数
def greet():
print("こんにちは!")
# --- デコレーターの適用 ---
# greet関数をsimple_loggerに渡し、機能が追加された新しい関数を受け取る
decorated_greet = simple_logger(greet)
# 新しい関数を実行
decorated_greet()
実行結果:
--- 処理開始 ---
こんにちは!
--- 処理終了 ---
greet 関数のコードを一切書き換えることなく、「処理開始」「処理終了」というログ出力を追加できました。これがデコレーターの基本的な仕組みです。
@ 構文による記述
Pythonでは、関数定義の直前に @デコレーター名 を記述することで、上記の「関数の置き換え」を自動的に行うことができます。これを「シンタックスシュガー(糖衣構文)」と呼びます。
# デコレーター定義(先ほどと同じ)
def simple_logger(func):
def wrapper():
print("--- 処理開始 ---")
func()
print("--- 処理終了 ---")
return wrapper
# @構文を使ってデコレーターを適用
@simple_logger
def say_bye():
print("さようなら!")
# そのまま呼び出すだけで、デコレーション済みの関数が実行される
say_bye()
実行結果:
--- 処理開始 ---
さようなら!
--- 処理終了 ---
@simple_logger と記述することは、say_bye = simple_logger(say_bye) を実行することと等価です。
引数と戻り値に対応する汎用的なデコレーター
上記の例における wrapper 関数は引数を受け取らないため、引数を必要とする関数に適用するとエラーになります。また、元の関数が値を返しても、それを return していないため、戻り値が失われてしまいます。
あらゆる関数に対応できる汎用的なデコレーターを作るには、可変長引数(*args, **kwargs)と return を使用します。
実践的なコード例:実行結果のログ出力
関数の引数と、実行結果(戻り値)を表示するデコレーターを作成します。
def result_logger(func):
"""
関数の引数と戻り値をログに出力するデコレーター
"""
# *args, **kwargs であらゆる引数を受け取れるようにする
def wrapper(*args, **kwargs):
print(f"[Log] 関数 '{func.__name__}' が呼び出されました。")
# 元の関数を実行し、戻り値を変数に格納
result = func(*args, **kwargs)
print(f"[Log] 戻り値: {result}")
# 戻り値を呼び出し元に返す
return result
return wrapper
# --- デコレーターの利用 ---
@result_logger
def add_numbers(a, b):
return a + b
@result_logger
def multiply(x, y):
return x * y
# 実行
sum_val = add_numbers(10, 20)
print(f"合計: {sum_val}\n")
product_val = multiply(5, 4)
print(f"積: {product_val}")
実行結果:
[Log] 関数 'add_numbers' が呼び出されました。
[Log] 戻り値: 30
合計: 30
[Log] 関数 'multiply' が呼び出されました。
[Log] 戻り値: 20
積: 20
解説
def wrapper(*args, **kwargs):: どんな引数でも受け取れるように定義します。result = func(*args, **kwargs): 受け取った引数をそのまま元の関数に渡して実行します。return result: 元の関数の実行結果を、ラッパー関数の戻り値として返します。これを忘れると、デコレーションされた関数は常にNoneを返すようになってしまいます。
まとめ
- デコレーターは、関数をラップして機能を追加する仕組みです。
def wrapper()の中で元の関数を呼び出し、そのwrapperを返します。@デコレーター名を使うことで、簡潔に適用できます。- 引数や戻り値を持つ関数に対応させるため、
wrapper関数では*args,**kwargsを受け取り、最後にreturnするのが定石です。
