函数定义的弊端
python是动态语言,变量随时可以被赋值,且能赋值成不同的类型
动态语言很灵活,但也有弊端
def add(x,y):return x+yprint(add(4,5))print(add('hello','world'))print(4,'hello') # 异常报错
- 难发现,由于不做类型检查,只能在运行时才能暴露问题
- 难使用,函数的使用者并不知道函数的设计,不知道该传什么类型的数据
解决方案
1、增加文档字符串
- 这只是一个惯例,不是强制标准
def add(x, y):''':param x: int:param y: int:return: int'''return x+y
2、函数参数类型注解
def add(x: int, y: int) -> int:""":param x:int:param y:int:return: int"""return x + yprint(help(add))print(add(4, 5))print(add.__annotations__)
Python3.5中引入对函数的参数进行类型注解,通过annotations可以查看
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
3、变量注解
变量注解在Python 3.6中引入
num:int = 3
以上方案均不是强制要求。
inspect模块
inspect模块的四大功能
- 类型检查
- 获取源码
- 获取类的方法和参数信息
- 解析堆栈
signature(callable)
获取签名(函数签名包含了一个函数的信息。包括函数名、参数类型、类和名称空间的其他信息)
import inspectdef add(x: int, y: int, *args, **kwargs) -> int:""":param x:int:param y:int:return: int"""return x + ysig = inspect.signature(add)print(sig) # 函数签名# (x:int, y:int, *args, **kwargs) -> intprint('params:', sig.parameters) # 函数签名参数的有序字典# params: OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])print('return:', sig.return_annotation) # 返回值类型# return: <class 'int'>print(sig.parameters['x'].annotation) # 参数x类型# <class 'int'>for item in sig.parameters.items():name, parm = itemprint(parm.kind)
上述示例中函数签名sig中有Parameter对象,同时包含以下属性
- 保存在元祖中,是只读的
- name,参数名字
- annotation,参数的注解
- default,参数的缺省值
kind,实参如何绑定到形参,就是形参的类型
- POSITIONAL_ONLY:值必须是位置参数提供
- POSITIONAL_OR_KEYWORD:值可以作为关键字或位置参数提供
- VAR_POSITIONAL:可变位置参数,对应*args
- VAR_KEYWORD:可变关键字参数,对用**kwargs
- KEYWORDONLY:对应或_args后出现的非可变关键字参数
inspect.isfunction(add), 是否为函数
inspect.ismethod(add), 是否是类的方法
inspect.isgenerator(add), 是否是生成器对象
inspect.ismodule(inspect), 是否是模块
函数参数类型检查示例
思路:
- 函数参数检查,一定是在函数外进行
- 函数应该作为参数,传入到检查函数中
- 检查函数拿到函数传入的实际参数,与形参进行对比
- _annotations**包括返回值类型的声明,但其属性是一个无序字典,假设要做位置参数的判断,无法和**annotations_中的声明对应
- 可使用inspect模块获取对象信息的函数签名,生成有序字典
import inspectfrom functools import wrapsdef check(fn):def kwcheck(val, obj):if isinstance(val, obj): # 进行位置参数类型比较print(val, 'is', obj)else:errstr = '{} {} {}'.format(val, 'is not', obj)print(errstr)raise TypeError(errstr)@wraps(fn)def wapper(*args, **kwargs):sig = inspect.signature(fn)params = sig.parameters # 获取函数签名的有序字典# 对位置参数的处理param_list = tuple(params.keys()) # 将签名中的keys生成元组和位置参数进行比较for i, v in enumerate(args):k = param_list[i] # 通过位置参数的索引在签名中获取到对应的keykwcheck(val=v, obj=params[k].annotation)# 对关键字参数的处理for k, v in kwargs.items(): # 获取关键字参数键值对kwcheck(val=v, obj=params[k].annotation)results = fn(*args, **kwargs)return resultsreturn wapper@checkdef add(x: int, y: int = 7):return x + yprint(add(8))
