Python デコレータ入門|関数を装飾する基礎概念
Pythonのデコレータの基本的な使い方から応用例まで解説。関数を装飾して機能を拡張する方法を初心者向けに説明します。
Python デコレータ入門|関数を装飾する基礎概念
同じような機能を何度も書いていて、もっと効率的にできないかと思いませんか?
みなさん、Pythonで関数を書いているとき、こんな悩みを抱えていませんか?
「関数の実行時間を測定したい」 「ログを出力する機能を追加したい」 「同じような処理を何度も書いている」
実は、デコレータを使うことで、既存の関数に新しい機能を簡単に追加できるんです。
この記事では、Pythonのデコレータの基本的な使い方から実践的な応用例まで、初心者にも分かりやすく解説します。 具体的なコード例とともに、段階的に理解できるよう丁寧に説明しますね!
デコレータって何?基本概念から理解しよう
デコレータは、関数やクラスを装飾(修飾)する機能のことです。
簡単に言うと、既存の関数に新しい機能を追加したり、動作を変更したりするための仕組みなんです。
最初の例を見てみよう
デコレータは@
記号を使って記述します。
装飾したい関数の上に配置するだけで使えます。
def my_decorator(func): """デコレータ関数""" def wrapper(): print("関数実行前の処理") func() print("関数実行後の処理") return wrapper
@my_decoratordef greet(): """挨拶する関数""" print("こんにちは!")
# 関数を実行greet()
このコードを実行すると、以下のような出力が得られます。
関数実行前の処理
こんにちは!
関数実行後の処理
@my_decorator
を付けることで、greet
関数に追加の機能を簡単に付与できました。
デコレータの基本的な流れ:
my_decorator
関数が元の関数を受け取るwrapper
関数で処理を包み込む- 実行前後に追加の処理を行う
- 最後に元の関数を実行
これにより、元の関数を変更することなく、新しい機能を追加できるんです。
デコレータの仕組みを詳しく見てみよう
関数は第一級オブジェクトなんです
Pythonでは、関数は「第一級オブジェクト」として扱われます。 これは、関数を変数に代入したり、他の関数の引数として渡したりできることを意味します。
def hello(): return "Hello, World!"
# 関数を変数に代入greeting = helloprint(greeting()) # 出力: Hello, World!
# 関数を引数として渡すdef call_function(func): return func()
result = call_function(hello)print(result) # 出力: Hello, World!
この例では、hello
関数をgreeting
変数に代入しています。
そして、call_function
にhello
関数を引数として渡しています。
関数が変数のように扱えるからこそ、デコレータが可能になるんです。
@記号の正体を理解しよう
デコレータの@
記号は、実際には以下の処理の糖衣構文(シンタックスシュガー)です。
def my_decorator(func): def wrapper(): print("処理前") func() print("処理後") return wrapper
# @記号を使った場合@my_decoratordef function1(): print("関数1")
# 上記と同等の処理def function2(): print("関数2")function2 = my_decorator(function2)
# 両方とも同じ動作function1()function2()
このコードを実行すると、どちらも同じ結果が出力されます。
@my_decorator
は、実際にはfunction1 = my_decorator(function1)
と同じ処理を行っているんです。
つまり、元の関数を新しい関数に置き換えているということですね。
引数を持つ関数にデコレータを適用してみよう
*argsと**kwargsで柔軟に対応
実際の関数は引数を持つことが多いため、デコレータも引数に対応する必要があります。
def log_decorator(func): """関数の実行をログに記録するデコレータ""" def wrapper(*args, **kwargs): print(f"関数 {func.__name__} を実行中...") print(f"引数: {args}, キーワード引数: {kwargs}") result = func(*args, **kwargs) print(f"戻り値: {result}") return result return wrapper
@log_decoratordef add(a, b): """2つの数を加算する関数""" return a + b
@log_decoratordef greet(name, greeting="こんにちは"): """挨拶を行う関数""" return f"{greeting}、{name}さん!"
# 使用例result1 = add(5, 3)result2 = greet("田中", greeting="おはよう")
このコードでは、*args
と**kwargs
を使って任意の引数を受け取れるようにしています。
*args
は位置引数を、**kwargs
はキーワード引数をまとめて受け取ります。
これにより、どんな引数の組み合わせでも対応できるんです。
実行すると、以下のような出力が得られます。
関数 add を実行中...
引数: (5, 3), キーワード引数: {}
戻り値: 8
関数 greet を実行中...
引数: ('田中',), キーワード引数: {'greeting': 'おはよう'}
戻り値: おはよう、田中さん!
functools.wrapsで関数情報を保持しよう
デコレータを使うと、元の関数の情報(名前、docstringなど)が失われてしまいます。
functools.wraps
を使うことで、この問題を解決できます。
import functools
def better_decorator(func): """改良されたデコレータ""" @functools.wraps(func) def wrapper(*args, **kwargs): print(f"実行中: {func.__name__}") return func(*args, **kwargs) return wrapper
@better_decoratordef calculate(x, y): """数値の計算を行う関数""" return x * y
# 元の関数の情報が保持されるprint(calculate.__name__) # 出力: calculateprint(calculate.__doc__) # 出力: 数値の計算を行う関数
@functools.wraps(func)
をつけることで、元の関数の__name__
や__doc__
が保持されます。
これにより、デコレータを使っても、元の関数の情報を確認できるようになります。
実際に役立つデコレータを作ってみよう
実行時間測定デコレータ
関数の実行時間を測定するデコレータを作ってみましょう。
import timeimport functools
def timing_decorator(func): """関数の実行時間を測定するデコレータ""" @functools.wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() execution_time = end_time - start_time print(f"{func.__name__} の実行時間: {execution_time:.4f}秒") return result return wrapper
@timing_decoratordef slow_function(): """時間のかかる処理をシミュレート""" time.sleep(1) return "処理完了"
@timing_decoratordef calculate_sum(n): """1からnまでの合計を計算""" return sum(range(1, n + 1))
# 使用例result1 = slow_function()result2 = calculate_sum(1000000)
このデコレータは、関数の実行前後で時間を測定しています。
time.time()
で現在時刻を取得し、実行前後の時間の差を計算しています。
これにより、関数の処理時間を簡単に測定できるんです。
キャッシュデコレータ
関数の結果をキャッシュして、同じ引数での再実行を高速化するデコレータです。
import functools
def cache_decorator(func): """関数の結果をキャッシュするデコレータ""" cache = {} @functools.wraps(func) def wrapper(*args, **kwargs): # キャッシュのキーを作成 key = str(args) + str(sorted(kwargs.items())) if key in cache: print(f"キャッシュから結果を取得: {func.__name__}") return cache[key] print(f"計算を実行: {func.__name__}") result = func(*args, **kwargs) cache[key] = result return result return wrapper
@cache_decoratordef fibonacci(n): """フィボナッチ数列を計算(再帰版)""" if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2)
# 使用例print(fibonacci(10)) # 初回は計算print(fibonacci(10)) # 2回目はキャッシュから取得
このデコレータは、引数をキーとして結果を辞書に保存します。
同じ引数で再度呼び出されたときは、保存された結果を返すため、計算時間を大幅に短縮できます。 フィボナッチ数列のような再帰的な計算で特に効果的です。
認証デコレータ
アクセス制御を行うデコレータの例です。
import functools
def require_auth(func): """認証が必要な関数用のデコレータ""" @functools.wraps(func) def wrapper(*args, **kwargs): # 簡単な認証チェック(実際にはより複雑な処理) if not hasattr(wrapper, 'authenticated'): print("認証が必要です。ログインしてください。") return None print("認証済み: 関数を実行します") return func(*args, **kwargs) return wrapper
@require_authdef access_sensitive_data(): """機密データにアクセスする関数""" return "機密情報: 重要なデータ"
# 認証前result1 = access_sensitive_data() # 認証エラー
# 認証後access_sensitive_data.authenticated = Trueresult2 = access_sensitive_data() # 実行成功
このデコレータは、関数の実行前に認証状態をチェックします。
認証されていない場合はエラーメッセージを表示し、認証されている場合のみ関数を実行します。 実際のアプリケーションでは、より複雑な認証ロジックを実装することになります。
パラメータ付きデコレータを使ってみよう
デコレータファクトリーの作成
デコレータ自体にパラメータを渡すことも可能です。
import functools
def repeat(times): """指定回数だけ関数を実行するデコレータファクトリー""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): results = [] for i in range(times): print(f"実行回数: {i + 1}") result = func(*args, **kwargs) results.append(result) return results return wrapper return decorator
@repeat(3)def greet(name): """挨拶を行う関数""" return f"こんにちは、{name}さん!"
# 使用例results = greet("田中")print(results)
このコードでは、repeat(3)
でデコレータファクトリーを呼び出しています。
repeat
関数はdecorator
関数を返し、そのdecorator
関数が実際のデコレータとして動作します。
これにより、パラメータを受け取るデコレータを作成できるんです。
条件付きデコレータ
時間帯に応じて実行を制御するデコレータです。
import functoolsfrom datetime import datetime
def only_during_hours(start_hour, end_hour): """指定時間帯のみ実行可能なデコレータ""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): current_hour = datetime.now().hour if start_hour <= current_hour < end_hour: return func(*args, **kwargs) else: print(f"この機能は{start_hour}時から{end_hour}時の間のみ利用可能です") return None return wrapper return decorator
@only_during_hours(9, 17)def business_function(): """営業時間中のみ実行可能な関数""" return "業務処理を実行中..."
# 使用例result = business_function()
このデコレータは、現在時刻をチェックして、指定された時間帯のみ関数を実行します。
営業時間外の場合は、エラーメッセージを表示して処理を停止します。 実際のシステムでは、メンテナンス時間の制御などに活用できます。
複数のデコレータを組み合わせよう
デコレータの重ね掛け
複数のデコレータを一つの関数に適用することができます。
import functoolsimport time
def timing_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"実行時間: {end - start:.4f}秒") return result return wrapper
def log_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"実行開始: {func.__name__}") result = func(*args, **kwargs) print(f"実行完了: {func.__name__}") return result return wrapper
@timing_decorator@log_decoratordef complex_calculation(n): """複雑な計算を行う関数""" time.sleep(0.1) # 処理時間をシミュレート return sum(i**2 for i in range(n))
# 使用例result = complex_calculation(1000)
このコードでは、timing_decorator
とlog_decorator
を組み合わせています。
実行すると、以下のような出力が得られます。
実行開始: complex_calculation
実行完了: complex_calculation
実行時間: 0.1001秒
実行順序の理解
デコレータは下から上に適用されます。
# 以下の書き方は@decorator_a@decorator_bdef func(): pass
# これと同等です# func = decorator_a(decorator_b(func))
つまり、decorator_b
が先に適用され、その結果にdecorator_a
が適用されます。
クラスベースのデコレータを作ってみよう
__call__メソッドを使用
クラスを使ってデコレータを作ることもできます。
import functoolsimport time
class TimingDecorator: """クラスベースのタイミングデコレータ""" def __init__(self, func): self.func = func self.call_count = 0 functools.update_wrapper(self, func) def __call__(self, *args, **kwargs): self.call_count += 1 start_time = time.time() result = self.func(*args, **kwargs) end_time = time.time() print(f"呼び出し回数: {self.call_count}") print(f"実行時間: {end_time - start_time:.4f}秒") return result
@TimingDecoratordef sample_function(n): """サンプル関数""" return sum(range(n))
# 使用例result1 = sample_function(1000)result2 = sample_function(2000)
クラスベースのデコレータでは、__call__
メソッドを定義することで、インスタンスを関数のように呼び出せます。
また、インスタンス変数を使って状態を保持できるため、呼び出し回数などの情報を管理できます。
パラメータ付きクラスデコレータ
import functools
class RetryDecorator: """リトライ機能付きデコレータ""" def __init__(self, max_retries=3): self.max_retries = max_retries def __call__(self, func): @functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(self.max_retries + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == self.max_retries: print(f"最大リトライ回数に達しました: {e}") raise print(f"リトライ {attempt + 1}/{self.max_retries}: {e}") return wrapper
@RetryDecorator(max_retries=2)def unreliable_function(): """失敗する可能性のある関数""" import random if random.random() < 0.7: raise ValueError("処理に失敗しました") return "成功!"
# 使用例try: result = unreliable_function() print(result)except ValueError as e: print(f"最終的に失敗: {e}")
このクラスデコレータは、関数の実行に失敗した場合に指定回数だけリトライします。
__init__
メソッドでリトライ回数を設定し、__call__
メソッドで実際のデコレータを返します。
組み込みデコレータを活用しよう
property, staticmethod, classmethod
Pythonには、いくつかの組み込みデコレータが用意されています。
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): """面積を計算するプロパティ""" import math return math.pi * self._radius ** 2 @staticmethod def is_valid_radius(radius): """有効な半径かどうかをチェック""" return radius > 0 @classmethod def from_diameter(cls, diameter): """直径から円を作成するクラスメソッド""" return cls(diameter / 2)
# 使用例circle = Circle(5)print(f"半径: {circle.radius}")print(f"面積: {circle.area}")
# クラスメソッドの使用circle2 = Circle.from_diameter(10)print(f"直径10の円の半径: {circle2.radius}")
# スタティックメソッドの使用print(f"半径5は有効: {Circle.is_valid_radius(5)}")
この例では、3つの組み込みデコレータを使用しています。
@property
:メソッドを属性のように扱えるようにします。
@staticmethod
:クラスに関連するが、インスタンスを必要としないメソッドです。
@classmethod
:クラス自体を第一引数として受け取るメソッドです。
これらのデコレータを使うことで、より柔軟なクラス設計が可能になります。
まとめ
Pythonのデコレータは、関数に機能を追加するための強力な仕組みです。 基本的な使い方から応用例まで、様々な場面で活用できます。
この記事で学んだ内容:
- デコレータは
@
記号を使って関数を装飾する - 関数の実行前後に処理を追加できる
- パラメータ付きデコレータで柔軟な制御が可能
- クラスベースのデコレータも作成できる
- 組み込みデコレータ(property、staticmethod等)も活用できる
デコレータを使うことで、コードの重複を削減し、関心の分離を実現できます。
最初は難しく感じるかもしれませんが、基本的なパターンを覚えれば簡単に使えるようになります。 ぜひ実際のプロジェクトで活用してみてください!