Pythonデコレータ完全攻略 – 基本から実践応用まで徹底解説

デコレータとは?3分で理解する基本概念

**デコレータ(Decorator)**は、既存の関数やクラスに新しい機能を追加するPythonの仕組みです。元のコードを変更せずに、ログ出力や実行時間計測などの機能を簡単に追加できます。

デコレータの基本構文

# デコレータなし
def hello():
    print("Hello, World!")

# デコレータあり
@my_decorator
def hello():
    print("Hello, World!")
Code language: PHP (php)

最も基本的なデコレータの作り方

シンプルなデコレータ

def my_decorator(func):
    def wrapper():
        print("処理開始")
        func()
        print("処理終了")
    return wrapper

@my_decorator
def greet():
    print("こんにちは!")

greet()  # 処理開始 → こんにちは! → 処理終了
Code language: PHP (php)

引数を持つ関数用のデコレータ

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"関数 {func.__name__} を実行")
        result = func(*args, **kwargs)
        print(f"結果: {result}")
        return result
    return wrapper

@logger
def add(a, b):
    return a + b

add(3, 5)  # 関数 add を実行 → 結果: 8
Code language: PHP (php)

実用的なデコレータ集

1. 実行時間計測デコレータ

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__}: {time.time() - start:.4f}秒")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "完了"
Code language: JavaScript (javascript)

2. リトライ機能デコレータ

def retry(max_attempts=3):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise e
                    print(f"リトライ {attempt + 1}/{max_attempts}")
        return wrapper
    return decorator

@retry(max_attempts=3)
def unstable_api_call():
    import random
    if random.random() < 0.7:
        raise ConnectionError("API接続失敗")
    return "成功"
Code language: PHP (php)

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

def cache(func):
    cached_results = {}
    @wraps(func)
    def wrapper(*args):
        if args in cached_results:
            print(f"キャッシュから取得: {args}")
            return cached_results[args]
        result = func(*args)
        cached_results[args] = result
        return result
    return wrapper

@cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
Code language: PHP (php)

パラメータ付きデコレータ

引数を受け取るデコレータ

def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hello():
    print("Hello!")

say_hello()  # Hello! が3回出力される
Code language: PHP (php)

条件付き実行デコレータ

def run_if(condition):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if condition():
                return func(*args, **kwargs)
            print(f"{func.__name__} はスキップされました")
        return wrapper
    return decorator

@run_if(lambda: datetime.now().hour < 18)
def work_hours_only():
    print("業務時間内の処理")
Code language: PHP (php)

クラス用デコレータ

クラスメソッドのデコレータ

class APIClient:
    @timer
    def fetch_data(self, url):
        import requests
        response = requests.get(url)
        return response.json()
    
    @retry(max_attempts=2)
    def post_data(self, url, data):
        import requests
        return requests.post(url, json=data)
Code language: CSS (css)

プロパティデコレータ

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
Code language: HTML, XML (xml)

高度なデコレータパターン

複数デコレータの組み合わせ

@timer
@retry(max_attempts=2)
@cache
def complex_calculation(n):
    import time
    time.sleep(0.1)  # 重い処理をシミュレート
    return n ** 2

# 実行順序: cache → retry → timer → complex_calculation
Code language: PHP (php)

クラスベースのデコレータ

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} が {self.count} 回目の呼び出し")
        return self.func(*args, **kwargs)

@CountCalls
def greet(name):
    return f"Hello, {name}!"

デコレータファクトリーパターン

def validate_types(**expected_types):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for arg_name, expected_type in expected_types.items():
                if arg_name in kwargs:
                    if not isinstance(kwargs[arg_name], expected_type):
                        raise TypeError(f"{arg_name} は {expected_type} である必要があります")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_types(name=str, age=int)
def create_user(name, age):
    return {"name": name, "age": age}
Code language: JavaScript (javascript)

実務でよく使うデコレータ活用例

1. API認証デコレータ

def require_auth(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not hasattr(wrapper, 'user') or not wrapper.user:
            raise PermissionError("認証が必要です")
        return func(*args, **kwargs)
    return wrapper

@require_auth
def get_user_data(user_id):
    return f"ユーザー {user_id} のデータ"
Code language: JavaScript (javascript)

2. ログ出力デコレータ

import logging
from datetime import datetime

def log_calls(level=logging.INFO):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logging.log(level, f"{datetime.now()}: {func.__name__} 開始")
            try:
                result = func(*args, **kwargs)
                logging.log(level, f"{datetime.now()}: {func.__name__} 成功")
                return result
            except Exception as e:
                logging.error(f"{datetime.now()}: {func.__name__} エラー: {e}")
                raise
        return wrapper
    return decorator

@log_calls(level=logging.DEBUG)
def process_data(data):
    return [x * 2 for x in data]
Code language: JavaScript (javascript)

3. レート制限デコレータ

import time
from collections import defaultdict

def rate_limit(calls_per_second=1):
    call_times = defaultdict(list)
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            now = time.time()
            func_calls = call_times[func.__name__]
            
            # 1秒以内の呼び出しをフィルタ
            recent_calls = [t for t in func_calls if now - t < 1]
            
            if len(recent_calls) >= calls_per_second:
                raise Exception("レート制限に達しました")
            
            call_times[func.__name__] = recent_calls + [now]
            return func(*args, **kwargs)
        return wrapper
    return decorator

@rate_limit(calls_per_second=2)
def api_call():
    return "API レスポンス"
Code language: PHP (php)

よくある間違いと対処法

1. functools.wrapsを忘れる

# ❌ 関数のメタデータが失われる
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# ✅ メタデータを保持
from functools import wraps

def good_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper
Code language: PHP (php)

2. クラスメソッドでのself引数の扱い

def method_decorator(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):  # selfを明示的に受け取る
        print(f"{self.__class__.__name__}.{func.__name__} 実行")
        return func(self, *args, **kwargs)
    return wrapper

class MyClass:
    @method_decorator
    def my_method(self, value):
        return value * 2
Code language: CSS (css)

パフォーマンスへの影響

軽量なデコレータの作り方

# ❌ 重いデコレータ
def heavy_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 毎回重い処理
        import time
        time.sleep(0.01)
        return func(*args, **kwargs)
    return wrapper

# ✅ 軽量なデコレータ
def light_decorator(func):
    # 初期化時に1回だけ実行
    func._call_count = 0
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        func._call_count += 1
        return func(*args, **kwargs)
    return wrapper
Code language: PHP (php)

デバッグとテスト

デコレータのテスト方法

def test_timer_decorator():
    @timer
    def sample_func():
        import time
        time.sleep(0.1)
        return "done"
    
    result = sample_func()
    assert result == "done"
    # タイマー出力を確認

def test_retry_decorator():
    attempt_count = 0
    
    @retry(max_attempts=3)
    def failing_func():
        nonlocal attempt_count
        attempt_count += 1
        if attempt_count < 3:
            raise Exception("失敗")
        return "成功"
    
    result = failing_func()
    assert result == "成功"
    assert attempt_count == 3
Code language: PHP (php)

まとめ:デコレータ活用のベストプラクティス

効果的な使用場面

  • 横断的関心事(ログ、認証、キャッシュ)
  • コードの重複削減
  • 機能の後付け追加

注意点

  • 過度な使用は可読性を損なう
  • デバッグが困難になる場合がある
  • パフォーマンスへの影響を考慮

推奨事項

  • functools.wrapsを必ず使用
  • シンプルで理解しやすいデコレータを心がける
  • 適切なテストを作成する

デコレータをマスターすることで、コードの再利用性と保守性が大幅に向上します。まずは基本的なタイマーやログ出力から始めて、徐々に複雑なパターンに挑戦していきましょう。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座