初めに
デコレーターは、関数を引数として受け取り、それを修飾(拡張または変更)してから返す別の関数です。
主に関数の再利用性を高め、コードをより読みやすく、管理しやすくするために使用されます。
デコレーターの基本
def my_decorator(func):
def wrapper():
# 何か前処理
func()
# 何か後処理
return wrapper
my_decorator はデコレータです。これは、func という関数を引数として受け取り、wrapper という新しい関数を返します。wrapper 関数内で、func が呼び出される前後に追加のコード(前処理や後処理)を実行することができます。
@my_decorator
def my_function():
print("Hello from my function!")
この例では、my_function 関数は my_decorator でデコレートされます。つまり、my_function を呼び出すときに実際には wrapper 関数が呼び出され、my_function の中身は wrapper 関数の中から呼び出されます。
引数と戻り値を持つデコレータ
引数や戻り値を持つ関数にデコレータを適用する場合、wrapper 関数内で *args と **kwargs を使用して、任意の数の位置引数やキーワード引数を受け取るようにします
def my_decorator(func):
def wrapper(*args, **kwargs):
# 何か前処理
result = func(*args, **kwargs)
# 何か後処理
return result
return wrapper
デコレータは関数の実行時間を計測したり、ログを出力したり、キャッシュやエラーハンドリングを行ったりと多様な用途に使用できます。
例えば、関数の実行時間を計測するデコレータは次のようになります。
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time} seconds to run.")
return result
return wrapper
@timing_decorator
def my_function():
# 長い処理
pass
このデコレータを使うことで、任意の関数の実行時間を簡単に計測できます。
各関数が実行される前に、特定の属性があるかチェックを行うデコレーター
def decorate_all_methods(method_decorator: Callable) -> Callable:
def class_decorator(cls):
for name, obj in vars(cls).items():
# コンストラクタ以外のメソッドをデコレートする
if callable(obj) and name != "__init__":
setattr(cls, name, method_decorator(obj))
return cls
return class_decorator
def check_logger(method: Callable) -> Callable:
@wraps(method)
def wrapper(*args, **kwargs):
_self = args[0]
# _self._loggerがセットされていない場合はdebugログを出す
if _self._logger is None:
print(
"Logger instance isn't set on the Module. "
"Please use it via ModuleHandler")
raise Exception
res = method(*args, **kwargs)
return res
return wrapper
decorate_all_methodsクラス
このクラスはデコレーターを引数に取ります。
デコレーター対象の呼び出し可能オブジェクト、つまりメソッドに対して、setattr
で引数のデコレータを追加しています。
つまり、setattr
によって、クラス内の各メソッドにデコレータが適用され、元のメソッド名でアクセスした際にデコレータを介して実行されるようになります。
check_logger
追加したいデコレーターの機能を定義しています。
decorate_all_methods
クラスでメソッド追加時には、wrapper
関数を返しているので、check_logger
内のwrapper
関数がメソッドに追加されることになります。
その追加されたwrapper
関数は_self._logger
属性が存在することを確認し、なければエラーを出しています。
存在する場合はmethod(*args, **kwargs)
で実際のメソッドが実行されます。
また@wraps
では、@wraps
でラップされた関数が、オリジナル(実際)の関数のメタデータを保持できるようになります。
今回だとmethod(*args, **kwargs)
のmethodは実際のメタデータが含まれているので実際のメソッドを実行することができるのです。