3.8 デコレーター
Pythonの「デコレーター(Decorator)」は、既存の関数のソースコードを直接書き換えることなく、新しい機能(前処理や後処理など)を追加(装飾)できる非常に強力な機能です。
ログ出力、実行時間の計測、ログイン認証のチェックなど、様々な関数で共通して使いたい処理を「デコレーター」として切り出すことで、コードの重複を防ぎ、可読性を高めることができます。
1. デコレーターの基本構文
Section titled “1. デコレーターの基本構文”デコレーターを使用するには、対象となる関数の直上の行に @デコレーター名 を記述します。
# すでに用意されているデコレーターを適用する例@timerdef calculate_sum(n): return sum(range(n))
# 関数を実行するだけで、@timerの機能(時間計測)が自動的に追加されるcalculate_sum(1000000)上記のコードは、内部的には calculate_sum = timer(calculate_sum) という処理を行っているのと同じです。つまり、関数を引数として受け取り、機能を追加した「新しい関数」を返しています。
2. デコレーターを自作してみよう
Section titled “2. デコレーターを自作してみよう”デコレーターの正体は、「関数を引数に受け取り、内部で定義した別の関数(ラッパー関数)を返す関数」です。
関数の実行前後にログを出力する簡単なデコレーター @logger を作ってみましょう。
def logger(func): # 機能を拡張するための「ラッパー関数」を定義 # *args, **kwargs を使うことで、どんな引数の関数にも対応できるようにする def wrapper(*args, **kwargs): print(f"--- 実行開始: {func.__name__} ---")
# 元の関数を実行し、結果を受け取る result = func(*args, **kwargs)
print(f"--- 実行終了: {func.__name__} ---") # 元の関数の戻り値をそのまま返す return result
# ラッパー関数自体を返す(実行はしないので () はつけない) return wrapper
# 作ったデコレーターを適用する@loggerdef greet(name): print(f"Hello, {name}!") return True
greet("Alice")# 出力:# --- 実行開始: greet ---# Hello, Alice!# --- 実行終了: greet ---3. デコレーターの重ね掛け
Section titled “3. デコレーターの重ね掛け”デコレーターは複数重ねて適用することができます。この場合、関数に近い方(下にあるもの)から順番に適用されていきます。
def bold(func): def wrapper(*args, **kwargs): return f"<b>{func(*args, **kwargs)}</b>" return wrapper
def italic(func): def wrapper(*args, **kwargs): return f"<i>{func(*args, **kwargs)}</i>" return wrapper
# 複数のデコレーターを適用@bold # 2番目に適用される@italic # 1番目に適用される(関数に最も近い)def get_text(text): return text
print(get_text("Hello"))# 出力: <b><i>Hello</i></b>実行順序としては bold(italic(get_text("Hello"))) のようになります。
4. 知っておくべきおまじない:functools.wraps
Section titled “4. 知っておくべきおまじない:functools.wraps”デコレーターを使うと、関数が「ラッパー関数(wrapper)」にすり替わってしまうため、元の関数の名前(__name__)やドキュメント文字列(docstring)などのメタデータが失われてしまうという問題があります。
これを防ぐために、標準ライブラリの functools.wraps をラッパー関数にデコレーターとして適用するのがベストプラクティスです。
from functools import wraps
def my_decorator(func): @wraps(func) # このおまじないをつける! def wrapper(*args, **kwargs): """これはラッパー関数です""" return func(*args, **kwargs) return wrapper
@my_decoratordef example_function(): """これはテスト関数です""" pass
# @wrapsのおかげで、元の関数の情報が保たれるprint(example_function.__name__) # 'example_function' と出力される('wrapper'にはならない)print(example_function.__doc__) # 'これはテスト関数です' と出力される5. 標準ライブラリでよく使うデコレーター
Section titled “5. 標準ライブラリでよく使うデコレーター”Pythonには最初から便利なデコレーターがいくつか用意されています。
- **
@classmethod/@staticmethod**: クラスのメソッドを、クラスメソッドや静的メソッドに変更します。 @property: クラスのメソッドを、属性(プロパティ)のようにアクセスできるようにします。@functools.lru_cache: 関数の戻り値をキャッシュ(記憶)し、同じ引数で呼び出されたときに再計算を省き、処理を高速化します。