デコレーターは、Python 2.4以降、関数型プログラミングを扱いやすくするためにシンタックスシュガーとして登場しました。関数を引数として受け取ったり、返したりする高階関数との作業を容易にするためです。機能を拡張するアプローチの進化により、@decoratorという簡潔で表現力豊かな形式が生まれました。
大規模なプロジェクトでは、しばしば関数を追加のロジックで修正したりラップしたりする必要があります。これには、ロギング、アクセスチェック、キャッシング、実行時間の測定が含まれます。デコレーターなしでは、手動でラッピング関数を呼び出す必要があり、コードが膨れ上がります。
デコレーターは、繰り返しの側面を独立したラッパーに抽出し、コードの可読性と再利用性を向上させることができます。@decoratorを使用することで、関数やメソッドに優雅に機能を追加できます。
コードの例:
import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # 実行にかかった時間を表示
主な特徴:
デコレーターはラップされた関数のシグネチャを変更できますか?
デコレーターは関数のシグネチャを変更しないと誤って考えられることが多いです。実際には、functools.wrapsを使用しないとメタ情報が失われ、自動ドキュメンテーションやイントロスペクションで予期しないエラーが発生する可能性があります。
コードの例:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper
デコレーターはパラメータ化できますか?
できないと答えることが多いですが、実際にはデコレーターを返す関数を通じてパラメータ化されたデコレーターを作成することができます。
コードの例:
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")
複数のデコレーターを1つの関数に適用できますか?実行順序は?
順序は重要でない、またはコード内のデコレーターの配置順と一致すると誤解されることがよくあります。実際には、最下層のデコレーターから適用され、次に上へ進みます。
コードの例:
def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core
10の関数に手動でロギングの処理時間を追加するため、コピーペーストで処理。
利点:
欠点:
タイミングロジックがデコレーターに移行され、メトリックが必要なすべての関数が@timing_decoratorでラップされています。
利点:
欠点:
functools.wrapsを利用しないと、シグネチャ情報が失われる可能性がある;