デコレーターしたい(@decorator
)
1@デコレーター名
2def 関数名(引数):
3 ...
@デコレーター名
で、関数やクラス本体の処理は変更せずに、補足処理を追加できます。
デコレーターを書きたい
1from functools import wraps
2
3def decorator_name(f):
4 @wraps(f)
5 def wrapper(*args, **kwargs)
6 ...前処理
7 result = f(*args, **kwargs) # 元の関数を実行
8 ...処理
9 return result # 元の関数の実行結果
10 return wrapper
デコレータを自作するときのテンプレートのようなものを用意しました。
デコレーターの正体は 高階関数 で、引数が関数オブジェクトになっていて、返り値も内部処理した関数オブジェクトになっています。
まず、デコレーター関数(decorator_name
)の引数を任意の関数(f
)とします。
そして、デコレーター関数の中に、
ラッパー関数(wrapper
)を作成します。
ラッパー関数の引数は可変長配列((*args, **kwargs)
)にし、
元の関数の引数を受け取れるようにします。
また、ラッパー関数の返り値は、元の関数の実行値にします。
最後に、ラッパー関数オブジェクトを、デコレーター関数の返り値とします。
このデコレーター関数(decorator_name
)の返り値を
ラッパー関数(wrapper
)オブジェクトにするのがポイントです。
ヒント
ラッパー関数には@functools.wraps
デコレーターを使っています。
このデコレーターを使うことで、
元の関数のメタデータ情報(__name__
、__doc__
など)を
引き継ぐ(残す?)ことができます。
以下では、作っておくとちょっと便利になるデコレーターを考えてみました。 動作確認していないものもあります。動かなかった場合は、教えてください。
ストップウォッチしたい
1import time
2from functools import wraps
3
4def timer(f):
5 @wraps(f)
6 def wrapper(*args, **kwargs):
7 start_time = time.perf_counter()
8 result = f(*args, **kwargs)
9 end_time = time.perf_counter()
10 elapsed_time = end_time - start_time
11 print(f"{f.__name__} executed in {elapsed_time:.4f} seconds")
12 return result
13 return wrapper
14
15@timer
16def some_function(...):
17 ...
18 return ...
関数の実行時間を計測したい場合のデコレーターです。
@timer
デコレーターで、任意の関数の実行時間を標準出力で確認できます。
time.perf_counter
を使うことで、より高精度な実行時間を取得できます。
また、CPUの実行時間だけを取得したい場合はtime.process_time
に置き換えればOKです。
リトライしたい
1import time
2from functools import wraps
3
4def retry(max_retries=3, delay=1):
5 def decorator(f):
6 @wraps(f)
7 def wrapper(*args, **kwargs):
8 retries = 0
9 while retries < max_retries:
10 try:
11 return f(*args, **kwargs)
12 except Exception as e:
13 retries += 1
14 time.sleep(delay)
15 raise Exception(f"Function {f.__name__} failed after {max_retries} retries.")
16 return wrapper
17 return decorator
18
19@retry
20def some_function(引数):
21 ...
22 return
例外が発生する可能性がある処理に対して、リトライ機能を追加するデコレーターです。