天天看點

裝飾器+inspect的python入參檢驗

由于 python 不支援函數入參類型的限定,是以,對于 python 來說,入參合法性檢測顯得尤為重要。

def add(a: int, b: int) -> int:
    return a + b

if __name__ == '__main__':
    print(add(1, "2"))

-------------------------------------------------------------
Traceback (most recent call last):
  File "D:/MyPython/checker/checker_param.py", line 9, in <module>
    print(add(1, "2"))
  File "D:/MyPython/checker/checker_param.py", line 5, in add
    return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
           

python inspect

需要介紹一下 PEP 3107 之 Function Annotations。這個是 python3 才支援的一個特性,可以為函數的參數進行注解,如下所示。

上文的add函數的int也可以寫成:

def add(x: 'integer', y: 'integer') -> 'the sum':
    return x + y
           

别人看到你的源碼的時候,通過注解,就可以你這個參數應該傳什麼進去,實際上,這些注釋是藏在 __annotations__ 字段裡的,通過如下指令可以顯示。

print(add.__annotations__)
{'x': 'integer', 'y': 'integer', 'return': 'the sum'}
           

字典 __annotations__ 裡的值可以是任何 object,可以是 list、tuple、函數等。

直接上代碼:

# coding=utf-8

import functools
import inspect

def check(name, value, checker):
    if isinstance(checker, (tuple, list, set)):
        return True in [check(name, value, sub_checker) for sub_checker in checker]
    elif checker is inspect._empty:
        return True
    elif checker is None:
        return value is None
    elif isinstance(checker, type):
        return isinstance(value, checker)
    elif callable(checker):
        result = checker(value)
        return result

def auto_type_checker(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        sig = inspect.signature(function)
        parameters = sig.parameters
        # fetch the argument name list.
        argument_list = list(parameters.keys())

        # fetch the parameter type list.
        type_list = [parameters[argument].annotation for argument in argument_list]

        # fetch the parameter value list.
        value_list = sig.bind(*args, **kwargs).arguments.values()

        # check the invalid argument, and raise the error.
        for argument, value, checker in zip(argument_list, value_list, type_list):
            if not check(argument, value, checker):
                raise Exception(f"arg '{argument}' required type:{checker},got type {type(value)}")

        # check the result.
        result = function(*args, **kwargs)
        checker = sig.return_annotation
        if not check('return', result, checker):
            raise Exception(['return'])

        # return the result.
        return result

    return wrapper

@auto_type_checker
def add(a: int, b: int) -> int:
    return a + b

if __name__ == '__main__':
    print(add.__annotations__)
    print(add(1, 4))
           

其中,auto_type_checker 是一個修飾器,在函數定義的時候調用即可。函數在聲明的時候,如果需要進行入參合法性校驗的話,就用如下文法為函數的輸入輸出指定 checker。

@auto_type_checker
def function(arg1: int, arg2, arg3: (int, float) = 0, arg4: lambda x: x > 0 = 1) -> list:
    return [arg1, arg2, arg3, arg4]
           

上述代碼有 4 種 checker:

  • arg1  <class 'int'>:type 型 checker,如 arg1,auto_type_checker 會檢測 arg1 是否是 int 型,如果不是,會抛出異常,而傳回值必須是 list 型,否則也會抛出異常;
  • arg2  <class 'inspect._empty'>:不指定 checker,如 arg2,auto_type_checker 不會為 arg2 進行合法性校驗;
  • arg3  (<class 'int'>, <class 'float'>):tuple/list 型 checker,如 arg3,tuple 或 list 中的所有元素都會被當作 checker,當所有 checker 都無法通過校驗,則抛出異常,上述代碼中,arg3 允許整數或浮點數,0為預設值。
  • arg4 <function <lambda> at 0x0000020126AD8730>:函數型 checker,如 arg4,auto_type_checker 會将 arg4 帶入到 checker,如果 checker 的傳回值是 Fasle,則抛出異常,上述代碼中,arg4 隻接受大于 0 的數字,1為預設值。;

測試:

print(function(1, 2, 3, 4))
print(function(1, 2, 3.0, 4))
print(function(1, 2, 3, -4))
************************************
[1, 2, 3, 4]
[1, 2, 3.0, 4]
    print(function(1, 2, 3, -4))
  File "D:/MyPython/checker/checker_param.py", line 39, in wrapper
    raise Exception(f"arg '{argument}' required type:{checker},got type {type(value)}")
Exception: arg 'arg4' required type:<function <lambda> at 0x0000026EC42C8730>,got type <class 'int'>
           

參考:

  • https://zhuanlan.zhihu.com/p/49078420
  • https://juejin.cn/post/6844903849963028487
  • https://vimsky.com/examples/detail/python-method-inspect.signature.html