Python デコレータパターン
デコレータパターンの核心はラッピングです。ラッパーオブジェクト(デコレータ)を作成して元のオブジェクトを包み込み、元のオブジェクトを変更せずに機能を拡張します。
基本概念
デコレータパターンには4つの主要な役割があります:
- コンポーネントインターフェース(Component):デコレータ対象とデコレータの共通インターフェースを定義します
- 具象コンポーネント(Concrete Component):デコレートされる元のオブジェクト
- デコレータ基底クラス(Decorator):コンポーネントオブジェクトへの参照を保持し、コンポーネントインターフェースを実装します
- 具象デコレータ(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")
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")
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}")
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)
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}")
@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}")
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}")
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 が発生します
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)) # 今回はキャッシュから取得されます
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()
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__}")
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()
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}")
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 デコレータは強力で柔軟なツールであり、以下の利点を提供します:
- コード再利用:ログ、キャッシュ、検証などの横断的関心事をビジネスロジックから分離します
- 動的拡張:元のコードを変更せずに新機能を追加します
- 簡潔さの維持:使用
@構文によりコードがより明確で読みやすくなります - 開放/閉鎖原則に適合:拡張に対して開き、修正に対して閉じる
デコレータを使用する際のベストプラクティス:
- 使用
functools.wraps関数のメタ情報を保持する - デコレータの単一責任を保つ
- デコレータ内の例外を適切に処理する
- デコレータの実行順序に注意する