Skip to content

3.8 デコレーター

Pythonの「デコレーター(Decorator)」は、既存の関数のソースコードを直接書き換えることなく、新しい機能(前処理や後処理など)を追加(装飾)できる非常に強力な機能です。

ログ出力、実行時間の計測、ログイン認証のチェックなど、様々な関数で共通して使いたい処理を「デコレーター」として切り出すことで、コードの重複を防ぎ、可読性を高めることができます。

デコレーターを使用するには、対象となる関数の直上の行に @デコレーター名 を記述します。

# すでに用意されているデコレーターを適用する例
@timer
def 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
# 作ったデコレーターを適用する
@logger
def greet(name):
print(f"Hello, {name}!")
return True
greet("Alice")
# 出力:
# --- 実行開始: greet ---
# Hello, Alice!
# --- 実行終了: greet ---

デコレーターは複数重ねて適用することができます。この場合、関数に近い方(下にあるもの)から順番に適用されていきます。

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_decorator
def 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: 関数の戻り値をキャッシュ(記憶)し、同じ引数で呼び出されたときに再計算を省き、処理を高速化します。