Pythonデコレータ入門
関数を拡張する強力な仕組み
デコレータを使えば、既存の関数に機能を追加したり、共通処理をまとめたりできます。基本の仕組みから実践的な活用パターンまでステップごとに解説します。
こんな人向けの記事です
- Pythonの@構文の意味を知りたい
- デコレータの仕組みを基礎から理解したい
- 実務で使える実践的なデコレータパターンを学びたい
Step 1デコレータとは何か
デコレータとは、既存の関数やクラスを変更せずに、機能を追加・拡張する仕組みです。Pythonでは関数が「第一級オブジェクト」であること(変数に代入したり、他の関数に渡したりできること)を活用しています。
デコレータは「関数を受け取って、新しい関数を返す関数」です。数学的に書くと decorator(func) → new_func というイメージです。
まずはデコレータを使わない形で、関数を別の関数で「包む」基本パターンを見てみましょう。
# デコレータの仕組みを理解するための基本例
def greet():
print("こんにちは!")
def add_greeting(func):
def wrapper():
print("--- 挨拶開始 ---")
func()
print("--- 挨拶終了 ---")
return wrapper
# 関数を「包む」
greet = add_greeting(greet)
greet()
# --- 挨拶開始 ---
# こんにちは!
# --- 挨拶終了 ---
この add_greeting(greet) の部分こそがデコレータの本質です。元の関数 greet を変更せずに、前後に処理を追加できました。
Step 2関数デコレータの基本(@構文)
Step 1で見た greet = add_greeting(greet) という書き方を、Pythonでは @ 構文でシンプルに書けます。
def add_greeting(func):
def wrapper():
print("--- 挨拶開始 ---")
func()
print("--- 挨拶終了 ---")
return wrapper
@add_greeting # greet = add_greeting(greet) と同じ意味
def greet():
print("こんにちは!")
greet()
# --- 挨拶開始 ---
# こんにちは!
# --- 挨拶終了 ---
@add_greeting を関数定義の直前に書くだけで、自動的に関数がラップされます。これがPythonのデコレータ構文です。
次に、引数を持つ関数にも対応できるようにしましょう。*args と **kwargs を使います。
def log_call(func):
def wrapper(*args, **kwargs):
print(f"関数 {func.__name__} が呼ばれました")
print(f" 引数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f" 戻り値: {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
result = add(3, 5)
# 関数 add が呼ばれました
# 引数: (3, 5), {}
# 戻り値: 8
wrapper関数で return func(*args, **kwargs) の return を忘れると、デコレートされた関数の戻り値が常に None になります。必ず戻り値を返しましょう。
Step 3引数付きデコレータ
デコレータ自体に引数を渡したい場合は、「デコレータを返す関数」を作ります。つまり関数を3重にネストします。
def repeat(n):
"""関数をn回繰り返すデコレータ"""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3) # repeat(3) が実行され、decorator が返される → @decorator
def say_hello():
print("Hello!")
say_hello()
# Hello!
# Hello!
# Hello!
@repeat(3) は2段階で処理されます。まず repeat(3) が実行されて decorator 関数が返り、次に @decorator が say_hello に適用されます。
実用的な例として、リトライ処理のデコレータを見てみましょう。
import time
def retry(max_attempts=3, delay=1):
"""失敗時にリトライするデコレータ"""
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
print(f"試行 {attempt} 失敗: {e}")
print(f"{delay}秒後にリトライ...")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def fetch_data(url):
# ネットワーク通信など失敗する可能性のある処理
import requests
return requests.get(url)
Step 4functools.wrapsの重要性
デコレータを使うと、元の関数の情報(名前やドキュメント文字列)が失われてしまう問題があります。
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet():
"""挨拶する関数"""
print("こんにちは")
# 元の関数の情報が失われている!
print(greet.__name__) # "wrapper"("greet"ではない)
print(greet.__doc__) # None("挨拶する関数"ではない)
__name__ や __doc__ が正しくないと、デバッグ時のトレースバックが読みにくくなり、help() 関数も正しい情報を返さなくなります。また、Djangoのビューデコレータなど、関数名に依存する処理が正常に動かなくなることもあります。
この問題を解決するのが 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 greet():
"""挨拶する関数"""
print("こんにちは")
# 元の関数の情報が保持される
print(greet.__name__) # "greet" ✓
print(greet.__doc__) # "挨拶する関数" ✓
デコレータを書くときは常に @functools.wraps(func) を付けるのがベストプラクティスです。たった1行追加するだけでデバッグ時の混乱を防げます。
Step 5クラスデコレータ
Pythonでは、__call__ メソッドを持つクラスもデコレータとして使えます。状態を保持したい場合に便利です。
from functools import wraps
class CountCalls:
"""関数の呼び出し回数をカウントするデコレータ"""
def __init__(self, func):
wraps(func)(self) # functools.wrapsをクラスで使う方法
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"{self.func.__name__} の呼び出し: {self.call_count}回目")
return self.func(*args, **kwargs)
@CountCalls
def say_hello(name):
print(f"Hello, {name}!")
say_hello("太郎") # say_hello の呼び出し: 1回目 → Hello, 太郎!
say_hello("花子") # say_hello の呼び出し: 2回目 → Hello, 花子!
print(say_hello.call_count) # 2
クラスデコレータを使えば、クラスそのものを拡張することもできます。
def add_repr(cls):
"""__repr__メソッドを自動追加するクラスデコレータ"""
def __repr__(self):
attrs = ", ".join(
f"{k}={v!r}" for k, v in self.__dict__.items()
)
return f"{cls.__name__}({attrs})"
cls.__repr__ = __repr__
return cls
@add_repr
class User:
def __init__(self, name, age):
self.name = name
self.age = age
user = User("太郎", 25)
print(user) # User(name='太郎', age=25)
Python 3.7以降の @dataclasses.dataclass はクラスデコレータの代表例です。__init__、__repr__、__eq__ などを自動生成してくれます。
Step 6実践的なデコレータ例
ここでは実務でよく使われる3つのデコレータパターンを紹介します。
1. ログ出力デコレータ
import logging
from functools import wraps
def log_function(logger=None):
"""関数の呼び出しと結果をログ出力するデコレータ"""
if logger is None:
logger = logging.getLogger(__name__)
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"開始: {func.__name__}(args={args}, kwargs={kwargs})")
try:
result = func(*args, **kwargs)
logger.info(f"完了: {func.__name__} → {result}")
return result
except Exception as e:
logger.error(f"エラー: {func.__name__} → {e}")
raise
return wrapper
return decorator
@log_function()
def calculate_total(items):
return sum(item["price"] for item in items)
2. 実行時間計測デコレータ
import time
from functools import wraps
def measure_time(func):
"""関数の実行時間を計測するデコレータ"""
@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}秒")
return result
return wrapper
@measure_time
def heavy_process():
"""重い処理のシミュレーション"""
total = sum(i * i for i in range(10_000_000))
return total
heavy_process()
# heavy_process: 0.8523秒
3. 認証チェックデコレータ(Django風)
from functools import wraps
def require_role(role):
"""指定された権限を持つユーザーのみアクセスを許可するデコレータ"""
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect("/login/")
if role not in request.user.roles:
return HttpResponseForbidden("権限がありません")
return func(request, *args, **kwargs)
return wrapper
return decorator
# Django のビュー関数での使用例
@require_role("admin")
def admin_dashboard(request):
return render(request, "admin/dashboard.html")
Djangoには @login_required、@permission_required、@csrf_exempt など多くの組み込みデコレータがあります。これらの内部実装も同じ仕組みです。
まとめチェックリスト
- デコレータは「関数を受け取って新しい関数を返す関数」
@構文はfunc = decorator(func)のシンタックスシュガー- 引数付きデコレータは3重ネスト(デコレータファクトリ)で作る
@functools.wraps(func)は常に付けるのがベストプラクティス__call__を持つクラスもデコレータとして使える- ログ出力・時間計測・認証チェックなど実務で活躍する場面は多い