Python デコレータパターン

デコレータパターンの核心はラッピングです。ラッパーオブジェクト(デコレータ)を作成して元のオブジェクトを包み込み、元のオブジェクトを変更せずに機能を拡張します。

基本概念

デコレータパターンには4つの主要な役割があります:

  1. コンポーネントインターフェース(Component):デコレータ対象とデコレータの共通インターフェースを定義します
  2. 具象コンポーネント(Concrete Component):デコレートされる元のオブジェクト
  3. デコレータ基底クラス(Decorator):コンポーネントオブジェクトへの参照を保持し、コンポーネントインターフェースを実装します
  4. 具象デコレータ(Concrete Decorator):具体的なデコレート機能を実装します


デコレータの基本構文

関数デコレータ

関数デコレータは最も一般的なデコレータの形式で、関数を引数として受け取り、新しい関数を返します。

例

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("関数実行前の何らかの操作")
        result = func(*args, **kwargs)
        print("関数実行後の何らかの操作")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

# デコレータを使用
say_hello("Alice")

出力結果:

函数执行前的一些操作
Hello, Alice!
函数执行后的一些操作

引数付きデコレータ

デコレータに引数を渡す必要がある場合は、もう1層関数をネストする必要があります:

例

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(times):
                print(f"{i+1} 回目の実行:")
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"こんにちは、{name}!")

greet("Bob")

クラスデコレータ

関数デコレータの他に、Python はクラスデコレータもサポートしています。クラスデコレータは__call__メソッドを実装することで動作します。

基本的なクラスデコレータ

例

class TimerDecorator:
    def __init__(self, func):
        self.func = func
   
    def __call__(self, *args, **kwargs):
        import time
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        print(f"関数 {self.func.__name__} の実行時間: {end_time - start_time:.4f} 秒")
        return result

@TimerDecorator
def calculate_sum(n):
    return sum(range(n))

result = calculate_sum(1000000)
print(f"計算結果: {result}")

引数付きクラスデコレータ

例

class LogDecorator:
    def __init__(self, level="INFO"):
        self.level = level
   
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"[{self.level}] 関数呼び出し: {func.__name__}")
            print(f"[{self.level}] 引数: args={args}, kwargs={kwargs}")
            result = func(*args, **kwargs)
            print(f"[{self.level}] 戻り値: {result}")
            return result
        return wrapper

@LogDecorator(level="DEBUG")
def multiply(a, b):
    return a * b

multiply(5, 3)

組み込みデコレータ

Python には便利な組み込みデコレータがいくつかあります:

@staticmethod と @classmethod

例

class Calculator:
    @staticmethod
    def add(x, y):
        return x + y
   
    @classmethod
    def multiply(cls, x, y):
        return x * y

# 静的メソッドを使用
result1 = Calculator.add(5, 3)
print(f静的メソッドの結果: {result1})

# クラスメソッドを使用
result2 = Calculator.multiply(5, 3)
print(f"クラスメソッドの結果: {result2}")

@property

例

class Circle:
    def __init__(self, radius):
        self._radius = radius
   
    @property
    def radius(self):
        return self._radius
   
    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("半径は正の数である必要があります")
        self._radius = value
   
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

circle = Circle(5)
print(f"半径: {circle.radius}")
print(f"面積: {circle.area}")

circle.radius = 10
print(f"新しい半径: {circle.radius}")
print(f"新しい面積: {circle.area}")

デコレータの実際の適用シナリオ

1. ログ記録

例

def log_execution(func):
    def wrapper(*args, **kwargs):
        print(f"実行開始: {func.__name__}")
        try:
            result = func(*args, **kwargs)
            print(f"正常完了: {func.__name__}")
            return result
        except Exception as e:
            print(f"実行失敗: {func.__name__}, エラー: {e}")
            raise
    return wrapper

@log_execution
def process_data(data):
    # データ処理のシミュレーション
    if not data:
        raise ValueError("データは空にできません")
    return [x * 2 for x in data]

# テスト
data = [1, 2, 3]
result = process_data(data)
print(f"処理結果: {result}")

2. 権限検証

例

def require_login(func):
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated', False):
            raise PermissionError("ユーザーがログインしていません")
        return func(user, *args, **kwargs)
    return wrapper

@require_login
def view_profile(user):
    return f"ユーザー {user['username']} のプロフィールを表示"

# テスト
user1 = {'username': 'alice', 'is_authenticated': True}
user2 = {'username': 'bob', 'is_authenticated': False}

print(view_profile(user1))  # 正常に実行
# print(view_profile(user2)) # PermissionError が発生します

3. キャッシュデコレータ

例

def cache_results(func):
    cache = {}
   
    def wrapper(*args):
        if args in cache:
            print(f"キャッシュから結果を取得: {args}")
            return cache[args]
        result = func(*args)
        cache[args] = result
        print(f"結果を計算してキャッシュ: {args} -> {result}")
        return result
    return wrapper

@cache_results
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# キャッシュ効果のテスト
print(fibonacci(5))
print(fibonacci(5))  # 今回はキャッシュから取得されます

複数のデコレータの実行順序

複数のデコレータを使用する場合、その実行順序は下から上へとなります:

例

def decorator1(func):
    def wrapper():
        print("デコレータ1 - 前")
        func()
        print("デコレータ1 - 後")
    return wrapper

def decorator2(func):
    def wrapper():
        print("デコレータ2 - 前")
        func()
        print("デコレータ2 - 後")
    return wrapper

@decorator1
@decorator2
def my_function():
    print("元の関数")

my_function()

出力結果:

装饰器1 - 前
装饰器2 - 前
原始函数
装饰器2 - 后
装饰器1 - 后

関数のメタ情報を保持する

デコレータを使用すると、元の関数のメタ情報(関数名、docstring など)がラッパー関数によって上書きされます。こちらを使用してfunctools.wrapsこれらの情報を保持できます:

例

from functools import wraps

def preserve_metadata(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """ラッパー関数のドキュメント文字列"""
        return func(*args, **kwargs)
    return wrapper

@preserve_metadata
def example_function():
    """これは元の関数のドキュメント文字列です"""
    pass

print(f"関数名: {example_function.__name__}")
print(f"ドキュメント文字列: {example_function.__doc__}")

実践演習

演習1:性能監視デコレータの作成

関数の実行時間とメモリ使用状況を監視するデコレータを作成します。

例

import time
import tracemalloc

def performance_monitor(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # メモリトラッキング開始
        tracemalloc.start()
       
        # 開始時間を記録
        start_time = time.time()
       
        # 関数を実行
        result = func(*args, **kwargs)
       
        # 終了時間を記録
        end_time = time.time()
       
        # メモリ使用状況を取得
        current, peak = tracemalloc.get_traced_memory()
        tracemalloc.stop()
       
        print(f"関数 {func.__name__} 性能レポート:")
        print(f"実行時間: {end_time - start_time:.4f} 秒")
        print(fメモリ使用: 現在 {current/1024:.2f} KB, ピーク {peak/1024:.2f} KB)
       
        return result
    return wrapper

@performance_monitor
def process_large_data():
    """大量のデータを処理するシミュレーション"""
    data = [i**2 for i in range(100000)]
    return sum(data)

process_large_data()

演習2:リトライ機構デコレータの実装

関数の実行が失敗したときに、指定された回数だけ自動的にリトライするデコレータを作成します。

例

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        print(f"関数 {func.__name__} の実行に失敗しました。最大リトライ回数に達しました")
                        raise
                    print(f"関数 {func.__name__} の {attempts} 回目の実行失敗: {e}")
                    print(f"{delay} 秒待機してからリトライ...")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_operation():
    """不安定な操作のシミュレーション"""
    import random
    if random.random() < 0.7:  # 70%の確率で失敗
        raise ValueError("ランダム失敗")
    return "操作成功"

# リトライ機構のテスト
result = unstable_operation()
print(f"最終結果: {result}")

まとめ

Python デコレータは強力で柔軟なツールであり、以下の利点を提供します:

  1. コード再利用:ログ、キャッシュ、検証などの横断的関心事をビジネスロジックから分離します
  2. 動的拡張:元のコードを変更せずに新機能を追加します
  3. 簡潔さの維持:使用@構文によりコードがより明確で読みやすくなります
  4. 開放/閉鎖原則に適合:拡張に対して開き、修正に対して閉じる

デコレータを使用する際のベストプラクティス:

  • 使用functools.wraps関数のメタ情報を保持する
  • デコレータの単一責任を保つ
  • デコレータ内の例外を適切に処理する
  • デコレータの実行順序に注意する
その他の拡張