デコレータとクロージャ
デコレータは Python の最もエレガントな機能の 1 つです。元のコードを変更せずに関数に「追加機能」を付け加えられます——ログ追加、タイミング、権限チェック……多くのフレームワークやライブラリがデコレータを広く使用しています。デコレータを理解することは、Python 習得への重要なマイルストーンです。
1. クロージャ
クロージャはデコレータの基礎です。簡単に言うと:内部関数が、外部関数の実行が終了した後でも、外部関数の変数を覚えていることです。
def make_multiplier(n):
"""乗算関数を作成"""
def multiplier(x):
return x * n # 内部関数が外部の変数 n を覚えている
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
print(double(10)) # 20
double と triple は同じファクトリ関数 make_multiplier から来ていますが、それぞれ異なる n の値(2 と 3)を覚えています。これがクロージャです——関数 + それが覚えている外部変数です。
2. デコレータの基本
デコレータは関数を引数として受け取り、新しい関数を返す関数です:
def my_decorator(func):
def wrapper():
print("=== Before function execution ===")
func()
print("=== After function execution ===")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
出力:
=== Before function execution ===
Hello!
=== After function execution ===
@my_decorator は say_hello = my_decorator(say_hello) と同等です。シンタックスシュガーによりコードがよりきれいになります。
例:ロギングデコレータ(難易度 ⭐⭐)
def log_call(func):
"""関数呼び出しをログに記録"""
def wrapper(*args, **kwargs):
print(f"[LOG] Calling {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} returned {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
@log_call
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(add(3, 5))
print(greet("Zhang San"))
出力:
[LOG] Calling add((3, 5), {})
[LOG] add returned 8
8
[LOG] Calling greet(('Zhang San',), {'greeting': 'Hello'})
[LOG] greet returned Hello, Zhang San!
3. functools.wraps:元の関数情報を保持
デコレータは元の関数を「置き換える」ため、関数名や docstring が失われます。@wraps がこれを修正します:
from functools import wraps
def my_decorator(func):
@wraps(func) # 元の関数情報を保持
def wrapper(*args, **kwargs):
"""Decorator inner function"""
print("Before function execution...")
return func(*args, **kwargs)
return wrapper
@my_decorator
def say_hello():
"""A simple greeting function"""
print("Hello!")
print(say_hello.__name__) # say_hello(@wraps がないと wrapper と表示)
print(say_hello.__doc__) # A simple greeting function(@wraps がないとデコレータ内部関数と表示)
@wraps を追加しましょう。 これがないと、デコレートされた関数の名前とドキュメントが失われ、デバッグが困難になります。functools から wraps をインポートし、内部のラッパー関数に @wraps(func) を適用します。
4. パラメータ化デコレータ
デコレータ自体にパラメータが必要な場合があります:
from functools import wraps
def repeat(times=2):
"""指定された回数だけ実行を繰り返す"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Zhang San")
出力:
Hello, Zhang San!
Hello, Zhang San!
Hello, Zhang San!
例:タイミングデコレータ(難易度 ⭐⭐⭐)
import time
from functools import wraps
def timer(func):
"""関数の実行時間を計測"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"[Timer] {func.__name__} took {elapsed:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
total = 0
for i in range(1000000):
total += i
return total
result = slow_function()
print(f"Result: {result}")
出力(タイミングはコンピュータによって異なります):
[Timer] slow_function took 0.0452 seconds
Result: 499999500000
5. 複数デコレータの組み合わせ
関数には複数のデコレータをスタックできます。実行順序は下から上です:
from functools import wraps
def bold(func):
@wraps(func)
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def hello():
return "Hello"
print(hello()) # <b><i>Hello</i></b>
# 実行順序:最初に @italic、次に @bold
# 以下と同等:hello = bold(italic(hello))
よくあるユースケース
- ロギング:各関数の呼び出しと戻り値を自動記録
- パフォーマンス計測:関数の実行時間を測定してボトルネックを特定
- 権限チェック:Web フレームワークで
@login_requiredデコレータがユーザーログイン状態をチェック - キャッシュ:
@functools.lru_cache()で関数結果を自動キャッシュ - リトライ:ネットワークリクエスト失敗時に指定回数自動リトライ
❓ よくある質問
*args と **kwargs は必須ですか?*args, kwargs ですべての引数を渡します。特定のシグネチャの関数のみをラップする場合は、特定のパラメータ名を指定できますが、汎用性は低くなります。汎用デコレータはほぼ常に *args, kwargs を使用します。func パラメータにアクセスできるようにします。@property(属性アクセス)、@staticmethod(静的メソッド)、@classmethod(クラスメソッド)、@functools.lru_cache()(キャッシュ)、@functools.wraps(関数情報保持)、@dataclass(データクラス)。すべて標準ライブラリに含まれています。📖 まとめ
- クロージャ:内部関数が外部関数の変数を、外部関数の実行終了後も覚えている
- デコレータ:関数を受け取り関数を返す関数。
@シンタックスシュガーを使用 @wraps(func)は元の関数の__name__と__doc__を保持——デコレータで常に使用- パラメータ化デコレータ:3 レベルのネスト——外側がパラメータ、中間が関数、内側がロジック
- 複数デコレータは下から上に適用
- デコレータは「関数の関数」——元のコードを変更せずに関数を拡張
📝 練習問題
-
初級(難易度 ⭐):
print_callデコレータを書いてください。関数実行時に「Calling xxx function」を表示します。 -
中級(難易度 ⭐⭐):
retry(max_attempts=3)デコレータを書いてください。関数が例外を発生させたときに、指定された回数だけ自動的にリトライします。リトライを使い切った場合は、最後の例外を再発生させます。ヒント: ラッパー内で for ループ + try-except を使用。 -
上級(難易度 ⭐⭐⭐):パラメータ化デコレータ
cache_result(ttl=60)を書いてください。関数の戻り値をキャッシュします。TTL 秒以内に同じパラメータで呼び出された場合はキャッシュされた結果を返し、TTL を超えた場合は値を再計算します。



