デコレータとクロージャ
クロージャとは
外側の関数のローカル変数を内側の関数が参照し続ける仕組みです。
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
デコレータの基本
デコレータは関数を受け取り、機能を追加した新しい関数を返す関数です。
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}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "完了"
result = slow_function() # slow_function: 1.0012秒
引数付きデコレータ
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(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}/{max_attempts}: {e}")
return wrapper
return decorator
@retry(max_attempts=3)
def unstable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("接続エラー")
return "成功"
よく使うデコレータパターン
ログ記録
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"呼び出し: {func.__name__}(args={args}, kwargs={kwargs})")
result = func(*args, **kwargs)
print(f"戻り値: {result}")
return result
return wrapper
キャッシュ(メモ化)
@functools.lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # 瞬時に計算
アクセス制御
def require_auth(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get("is_authenticated"):
raise PermissionError("認証が必要です")
return func(user, *args, **kwargs)
return wrapper
@require_auth
def get_secret_data(user):
return "秘密のデータ"
クラスデコレータ
def singleton(cls):
instances = {}
@functools.wraps(cls, updated=())
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self):
print("DB接続を確立")
db1 = DatabaseConnection() # DB接続を確立
db2 = DatabaseConnection() # 何も出力されない
print(db1 is db2) # True
まとめ
- クロージャは外側の変数を保持する関数
- デコレータは
@構文で関数やクラスを拡張する functools.wrapsで元の関数情報を保持するfunctools.lru_cacheで簡単にメモ化できる- 引数付きデコレータは3重の関数ネストになる