記事一覧へ戻る 本の順番で続きを読む

デコレータとクロージャ - 関数を拡張する

Python3中級 | 2026/02/18 15:44

デコレータとクロージャ

クロージャとは

外側の関数のローカル変数を内側の関数が参照し続ける仕組みです。

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重の関数ネストになる