はじめに
デコレータは、既存の関数やクラスの動作を変更せずに機能を追加するPythonの強力な仕組みです。@ 構文で宣言的に適用でき、ログ記録、実行時間計測、リトライ処理、キャッシュなど横断的関心事の実装に広く使われています。
本記事では、デコレータの基礎から実践的なパターンまでをコード例とともに解説します。
前提知識:第一級関数とクロージャ
Pythonでは関数はオブジェクトであり、変数に代入したり、他の関数に渡したり、関数から返したりできます。
def greet(name):
return f"Hello, {name}"
say_hello = greet # 関数を変数に代入
print(say_hello("Alice")) # "Hello, Alice"
クロージャは、外側の関数のスコープにある変数を参照する内部関数です。外側の関数が終了した後も、その変数にアクセスできます。
def make_multiplier(factor):
def multiplier(x):
return x * factor # factorを参照(クロージャ)
return multiplier
double = make_multiplier(2)
print(double(5)) # 10
基本的なデコレータ
デコレータは「関数を受け取り、関数を返す関数」です。
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
# @my_decorator は add = my_decorator(add) と等価
print(add(3, 4))
# 出力:
# Calling add
# Finished add
# 7
functools.wraps の重要性
デコレータを適用すると、元の関数のメタデータ(__name__、__doc__)がラッパー関数のものに置き換わります。functools.wraps でこれを防ぎます。
import functools
def my_decorator(func):
@functools.wraps(func) # 元の関数のメタデータを保持
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a, b):
"""二つの数を足す"""
return a + b
print(add.__name__) # "add"(wrapsなしだと"wrapper")
print(add.__doc__) # "二つの数を足す"
引数付きデコレータ
デコレータ自体に引数を渡したい場合、3重のネスト構造になります。
import functools
def repeat(n):
"""関数をn回繰り返すデコレータ"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(n):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(3)
def greet(name):
return f"Hello, {name}"
print(greet("Alice")) # ["Hello, Alice", "Hello, Alice", "Hello, Alice"]
実践パターン
実行時間計測
import functools
import time
def timer(func):
"""関数の実行時間を計測するデコレータ"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__}: {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "done"
slow_function() # "slow_function: 1.0012s"
リトライ(指数バックオフ付き)
import functools
import time
def retry(max_attempts=3, base_delay=1.0):
"""失敗時にリトライするデコレータ(指数バックオフ)"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
delay = base_delay * (2 ** attempt)
print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay}s...")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, base_delay=0.5)
def unreliable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("API unavailable")
return {"status": "ok"}
簡易キャッシュ
import functools
def simple_cache(func):
"""結果をキャッシュするデコレータ"""
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@simple_cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # キャッシュなしだと非現実的な計算時間
実用的には functools.lru_cache が同等の機能を提供します。
@functools.lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
クラスベースのデコレータ
__call__ メソッドを実装したクラスもデコレータとして使えます。状態を保持したい場合に有用です。
import functools
class CountCalls:
"""関数の呼び出し回数をカウントするデコレータ"""
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
print(f"Called {say_hello.count} times") # "Called 2 times"
デコレータのスタック
複数のデコレータは下から上に適用され、実行時は上から下に呼ばれます。
@timer
@retry(max_attempts=2)
def api_call():
pass
# 等価: api_call = timer(retry(max_attempts=2)(api_call))
# 実行時: timer → retry → api_call
組み込みデコレータ
Python標準の代表的なデコレータです。
| デコレータ | 用途 |
|---|---|
@property | メソッドをプロパティとしてアクセス |
@staticmethod | インスタンス不要のメソッド |
@classmethod | クラスを第一引数に受けるメソッド |
@functools.lru_cache | 結果のメモ化キャッシュ |
@functools.wraps | デコレータ内でメタデータを保持 |
@dataclasses.dataclass | データクラスの自動生成 |
関連記事
- pythonでprintの上書きをする方法 - Pythonの実践的なTipsを紹介しています。
- PythonのMatplotlibで3Dアニメーション(GIF)を作成する方法 - Matplotlibの実践的な使い方を紹介しています。
- Python環境構築 - Pythonの開発環境セットアップを解説しています。
参考文献
- Python公式ドキュメント: functools
- Ramalho, L. (2022). Fluent Python (2nd ed.). O’Reilly Media. Chapter 9: Decorators and Closures.
- PEP 318 – Decorators for Functions and Methods