はじめに
筆者は卒業研究の中でプログラムの実行時間を計測したいなと思っていました。プログラム中にtime.time()
で現在時刻を取得し、差分計算をしても良いのですが デコレータを使うともっと楽に実装し、使い回しも容易そうだなと思ったので実装してみました。
実装ついでデコレータについて調べてみたので備忘録としてまとめてみます。
実装するもの
今回は関数の実行時間を測定するtimer_decoratorを自作してみようと思います。
全体はこんな感じです。
import time
from functools import wraps
from typing import Callable
def timer_decorator() -> Callable:
"""
関数の実行時間を計測するデコレータ関数
Returns:
Callable: デコレータ関数
"""
def decorator(func: Callable) -> Callable:
@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
return decorator
また利用方法はこのようにします。
@timer_decorator()
def sample_function(x: int, y: int) -> int:
time.sleep(4) # 適当に時間がかかるようにする
return x + y
result = sample_function(x=5, y=3)
print(f"結果: {result}")
デコレータとは
デコレータとは、既存の関数に新たな機能を追加するための仕組みです。
関数のコードを変更せずに、実行前後に処理を追加したり、引数や戻り値の値を操作するものです。
たとえば、今回であればsample_function
という関数自体の実装を変更することなく@timer_decorator()
というデコレータを関数上部に記述することで、時間計測の処理を関数の処理に追加することができます。
よく見る例だと、FastAPIやFlaskなどで見られる
以下のような記述が、
@router.get("/hello")
async def hello():
"""hello function"""
return {"message": "hello FastAPI!"}
また、dataclass
, dataclass_json
などに見られる
from dataclasses import dataclass
from dataclasses_json import LetterCase, dataclass_json
@dataclass_json(letter_case=LetterCase.CAMEL)
@dataclass
class Hoge
attr1: str
attr1: str
のような記述もデコレータです。
dataclassのようにclass
にもデコレータを作成でき、これはクラスデコレータと呼ばれるようです。
デコレータを使うメリット
デコレータを利用するメリットには以下のようなものがあると考えます。
- 再利用正の向上
- 可読性の向上
- 関心の分離
何でもかんでもデコレータにするのは良くないですが、実装の選択肢として簡単な書き方は知っておいて損がないと考えます(存在と概要さえ知っておけば、AIに書いてもらえますしね♪)
timer_decoratorの実装
では実際に今回のdecoratorを実装してみます。
改めて私の実装を見てみましょう。
def timer_decorator() -> Callable:
"""
関数の実行時間を計測するデコレータ関数
Returns:
Callable: デコレータ関数
"""
def decorator(func: Callable) -> Callable:
@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
return decorator
decorator()
関数はデコレートする対象となる関数を受け取り、wrapper()
関数を返します。
wrapper()
関数では全体として、以下の処理を行っています。
- time.time()で開始時刻を取得
- デコレートする対象を実行し
result
に取得 - time.time()で終了時刻を取得する
- 実行時間の計測(execution_time)
- 実行時間の表示と、デコレート関数の実行結果を返す
ではここで出てきた@wraps(func)
の役割はなんなのでしょうか?これがなければ一見するとかなり冗長なコードに見えますよね。
@wraps(func)
はデコレートされた関数の関数名やdocstring(メタデータ)を保持します。
簡単にいうと、wrapper()
関数から元の関数の情報にアクセスすることを目的としています。
今回はfunc.__name__
を参照するために利用していますが、参照の必要がなければ不要な記述です。
まとめ
この記事では、timer_decoratorの実装を通して、Pythonのデコレータについて解説しました。
今回のtimer_decoratorの実装を応用することで、関数のログ出力やエラー処理、キャッシュなども実装できると考えています!
シンプルなログ出力
import logging
def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"{func.__name__} が呼び出されました。 引数: {args}, {kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} が終了しました。 戻り値: {result}")
return result
return wrapper
記事中の誤りや誤字脱字等がありましたら、編集リクエストやコメント等でやさしくご指摘お願いいたします!
最後までお読みいただきありがとうございました!