【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