概要
- ラップする関数への呼び出しの前後で追加コードを実行可能
- 入力の引数や戻り値にアクセスして値を変更したり、例外を送出可能
- デバッグ、関数登録などに役立つ
- 自作でデコレータを定義する場合はfunctoolsのデコレータwrapsを使用
- 使用しない場合はhelpなどが意図しない振る舞いを行う
functoolsのデコレータwrapsを未使用
example.py
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
"""関数デコレータ
functoolsのデコレータwrapsを未使用
"""
def trace(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f"{func.__name__}({args!r}, {kwargs!r}) -> {result!r}")
return result
return wrapper
@trace
def fibonacci(n):
"""フィボナッチ数のn番目の項目を返す
Parameters
----------
n: int
項数
Returns
-------
int
フィボナッチ数
"""
if n in {0, 1}:
return n
return fibonacci(n - 2) + fibonacci(n - 1)
if __name__ == "__main__":
fibonacci(5)
help(fibonacci)
$ python example.py
fibonacci((1,), {}) -> 1
fibonacci((0,), {}) -> 0
fibonacci((1,), {}) -> 1
fibonacci((2,), {}) -> 1
fibonacci((3,), {}) -> 2
fibonacci((0,), {}) -> 0
fibonacci((1,), {}) -> 1
fibonacci((2,), {}) -> 1
fibonacci((1,), {}) -> 1
fibonacci((0,), {}) -> 0
fibonacci((1,), {}) -> 1
fibonacci((2,), {}) -> 1
fibonacci((3,), {}) -> 2
fibonacci((4,), {}) -> 3
fibonacci((5,), {}) -> 5
# help関数の結果
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
(END)
functoolsのデコレータwraps
example.py
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
"""関数デコレータ
functoolsのデコレータwrapsを使用
"""
from functools import wraps
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f"{func.__name__}({args!r}, {kwargs!r}) -> {result!r}")
return result
return wrapper
@trace
def fibonacci(n):
"""フィボナッチ数のn番目の項目を返す
Parameters
----------
n: int
項数
Returns
-------
int
フィボナッチ数
"""
if n in {0, 1}:
return n
return fibonacci(n - 2) + fibonacci(n - 1)
if __name__ == "__main__":
fibonacci(5)
help(fibonacci)
$ python example.py
fibonacci((1,), {}) -> 1
fibonacci((0,), {}) -> 0
fibonacci((1,), {}) -> 1
fibonacci((2,), {}) -> 1
fibonacci((3,), {}) -> 2
fibonacci((0,), {}) -> 0
fibonacci((1,), {}) -> 1
fibonacci((2,), {}) -> 1
fibonacci((1,), {}) -> 1
fibonacci((0,), {}) -> 0
fibonacci((1,), {}) -> 1
fibonacci((2,), {}) -> 1
fibonacci((3,), {}) -> 2
fibonacci((4,), {}) -> 3
fibonacci((5,), {}) -> 5
# help関数の結果
Help on function fibonacci in module __main__:
fibonacci(n)
フィボナッチ数のn番目の項目を返す
Parameters
----------
n: int
項数
Returns
-------
int
フィボナッチ数
(END)
diff
example.py
--- functoolsのデコレータwrapsを未使用
+++ functoolsのデコレータwraps
@@ -8,10 +8,11 @@
__version__ = "1.0.1"
__date__ = "2022/12/08(Created: 2022/12/08)"
-
+from functools import wraps
def trace(func):
+ @wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f"{func.__name__}({args!r}, {kwargs!r}) -> {result!r}")
引数を受け取るデコレータの実装
example.py
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
"""引数が受け取るデコレータ
"""
from collections.abc import Callable
from functools import wraps
from inspect import signature
from typing import Any, ParamSpec
P = ParamSpec("P")
def get_arg_value(func: Callable[P, Any], args: tuple, name: str) -> Any:
"""関数の引数から指定した名前の値を取得
Args:
func (Callable[P, Any]): 検索対象の関数
args (tuple): 取得したい引数の値
name (str): 取得したい引数名
Raises:
Exception: 関数の中に取得したい引数名が存在しない場合
Returns:
Any: 引数の値
"""
sig = signature(func)
keys = tuple(sig.parameters.keys())
if name not in keys:
raise Exception(f"関数の中に引数{name}が存在しません")
return args[keys.index(name)]
def deco_func(
app_name: str, id_name: str = "request_id"
) -> Callable[[Callable[P, int]], Callable[P, int]]:
"""デコレータの引数を受け取る部分
Args:
app_name (str): 出力したいAPIの引数名
id_name (str, optional): 出力したいIDの引数名
"""
def _deco_func(func: Callable[P, int]) -> Callable[P, int]:
"""デコレータの関数を受け取る部分
Args:
func (Callable[P, int]): デコレータの関数を受け取る部分
Raises:
Exception: 戻り値の型がintと異なる場合
"""
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> int:
"""関数の前後に処理を差し込む部分
Raises:
Exception: 戻り値の型がstrと異なる場合
Returns:
int: 関数を実行した結果
"""
app = get_arg_value(func, args, app_name)
id = get_arg_value(func, args, id_name)
print(f"[{id}][START] POST {app} API is called.")
result = func(*args, **kwargs)
print(f"[{id}][END] POST {app} API is called.")
if not isinstance(result, int):
raise Exception(f"{type(result)}: 戻り値の型が仕様と異なります")
if result == 200:
print("[INFO] 成功")
else:
print("[ERROR] 失敗")
return result
return wrapper
return _deco_func
@deco_func("application")
def test(db: Any, request_id: Any, application: Any, result: int) -> int:
"""実行例用のスタブ関数"""
print("関数の処理を実行")
return result
if __name__ == "__main__":
test(1, "ID_hoge", "test", 200)
$ python example.py
[ID_hoge][START] POST test API is called.
関数の処理を実行
[ID_hoge][END] POST test API is called.
[INFO] 成功
参考
-
フォーマット済み文字列リテラル
-
書式指定ミニ言語仕様
- '!a' は ascii()
- '!s' は str()
- '!r' は repr()
-
書式指定ミニ言語仕様
- inspect --- 活動中のオブジェクトの情報を取得する