【Python】デコレータで引数の型チェックを実装する
2023 年 7 月 31 日 by tomokiyデコレータとは
一番よく目にするデコレータはプロパティのゲッターになるものですかね。
@property
def name(self) -> str:
return self.__name
このメソッドの上に@propertyのように記述しているものをデコレータと言います。
デコレータの仕組み
以下のようなコードを考えてみます。
def method(func):
def execute(*args, **kwargs):
print("処理開始")
print("引数は、", *args, **kwargs)
func(*args, **kwargs)
print("処理終了")
return execute
@method
def test(num:int):
print(num**2)
test(5)
test()を実行すると、まずmethod()が呼び出されます。 method()の引数funcには、test()がfunction型として入っており、 ラップされているexecute()の引数*args, **kwargsには、 デコレータを使用したtest()の引数が全て入ってきます。 なので、func(*args, **kwargs)を実行したタイミングで、 test()の中身が実行されることになります。
これを実行するとこうなります。
$ python test.py
処理開始
引数は、 5
25
処理終了
引数の型チェックをするデコレータを実装する
import inspect
import functools
# ログ文字列
LOG_STR_PARAM_TYPE_ERROR = "引数[{}]の型が異なります。[{}]ではなく[{}]で指定してください。"
def method(func):
"""
## メソッド用デコレータ
メソッドに本デコレータを必ず実装する
"""
def args_type_check(*args, **kwargs):
"""
## 引数の型チェック
引数がアノテーションで指定した型と一致しているかのチェックを行う。
"""
# メソッドのシグネチャ
sig = inspect.signature(func)
# 引数を繰り返す
for arg_key, arg_val in sig.bind(*args, **kwargs).arguments.items():
# 引数の本来の型
annotation = sig.parameters[arg_key].annotation
# 渡されてきた引数の型
arg_type = type(arg_val)
# 引数の型が型クラスでない場合は型チェックを不要にする
if type(annotation) is type and annotation is not inspect._empty and arg_type is not annotation:
raise TypeError(LOG_STR_PARAM_TYPE_ERROR.format(arg_key,arg_type,annotation))
return
@functools.wraps(func)
def execute_method(*args, **kwargs):
"""
## メソッド実行
メソッドを実行する。
"""
# 引数の型チェック
args_type_check(*args, **kwargs)
# 呼び出し元のメソッドの実行
result = func(*args, **kwargs)
return result
return execute_method
@method
def test(num:int):
return num**2
print(test(5))
上記をデコレータとして指定することで、引数の型チェックが行えます。
例えば、以下のようにtest()の引数numは整数型なので、5を入れると正常に計算されます。
@method
def test(num:int):
return num**2
print(test(5))
$ python test.py
25
文字列として入れてみると、このようなエラーになります。
@method
def test(num:int):
return num**2
print(test("5"))
$ python test.py
Traceback (most recent call last):
File "C:\Users\tomokiy\Desktop\test.py", line 48, in <module>
print(test("5"))
File "C:\Users\tomokiy\Desktop\test.py", line 37, in execute_method
args_type_check(*args, **kwargs)
File "C:\Users\tomokiy\Desktop\test.py", line 27, in args_type_check
raise TypeError(LOG_STR_PARAM_TYPE_ERROR.format(arg_key,arg_type,annotation))
TypeError: 引数[num]の型が異なります。[<class 'str'>]ではなく[<class 'int'>]で指定してください。
引数の型チェックを行うデコレータの処理の流れ
test()に指定した@methodが処理をキャッチします。return execute_methodによってdef execute_method(*args, **kwargs):が実行されます。args_type_check(*args, **kwargs)で引数の型チェックを行います。sig = inspect.signature(func)で、メソッドのシグネチャというのは、引数やアノテーションなどの情報を持っています。sig.bind(*args, **kwargs).arguments.items()をfor文で繰り返していますが、ここでそれぞれの引数を順番にチェックしています。- 引数の型がおかしい場合は
raise TypeErrorで型エラーにしています。 - 引数チェックが正常な場合は、
result = func(*args, **kwargs)で元々のメソッドを実行します。 return result戻り値を返してやります。これが無いとtest()の戻り値が機能しません。
★補足★
@functools.wraps(func)をつけることで、デコレータの引数funcから関数名__name__などを取得したときに、元々の関数test()の情報を取得することができます。つけないとexecute_method()の情報になってしまいます。
タグ: Python

