函数参数注解和作用, 以及参数注解在业务中的应用, inspect模块的使用
函数参数注解
函数定义的弊端:
- Python是动态语言,变量随时可以被赋值,且能被赋值为不同的类型
- Python不是静态编译型语言,变量的类型实在运行期间决定的
- 动态语言很灵活,但是这种特性也是有弊端的
举例:
1 | def add(x,y): |
问题:
- 难发现:由于不做任何的类型检查,直至运行期问题才显现出来,或者线上运行时才能暴露出现的问题
- 难使用:函数的使用者看到函数类型时,并不知道你的函数的设计,所以也不知道应该传入什么类型的数据
弊端的解决途径
文档注释
- 增加文档 Documentation String
- 这不是一个管理,不是强制标准,但是,建议程序员一定要为函数提供说明文档
- 但是 函数定义更新后,函数文档未必同步更新
1 | def add(x,y): |
函数注解 Annotations
如何解决这种动态语言定义的弊端呢?
通常使用的就是函数注解 Annotations
1 | def add(x:int,y:int) ->int: # 函数参数、函数返回值进行注解 |
Help on function add in module __main__:
add(x: int, y: int) -> int
:param x:
:param y:
:return:
None
****************************************
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
函数注解的属性保留
由以上代码可知,函数注解都是保存在函数annotations中,函数注解到底是怎么回事呢?
- Python3,5引入函数注解功能
- 对函数的参数进行类型注解
- 对函数的返回值进行类型注解
- 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
- 可以提供给第三发工具,做代码分析,发现隐藏的bug
- 函数注解的信息,保存在annotations属性中
- 根据函数注解的形式,即可知道存储在字典中的
1 | def add(x:int,y:int) ->int: |
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
函数参数类型的检查应用
函数参数类型检查思路:
- 函数参数的检查,一定是函数外部检查
- 函数应该作为参数,传入到检查函数中 [装饰器]
- 检查函数拿到函数传入的实际参数,与形参什么对比
- annotations属性是一个字典,其中包含返回值类型是声明,假设要做位置参数的判断,就无法与字典的声明对应,使用inspect模块
- inspect模块提供获取对象信息的函数,可以做到对 函数和类的的类型检测
inspect模块的参数检查功能
在使用inspect模块检查参数时,首先要对函数签名、签名的参数、参数中的元素要有清晰的认知,要从架构上把控他们之间的管理
- 签名对象:
签名: 包含函数信息,如函数名、函数参数类型、命名空间等常用属性
例如:‘bind’,‘bind partial’,‘empty’,‘parameters’,‘replace’,‘return_annotation’
- 参数对象:
签名属性的参数:包含参数属性,有序字典OrderedDict
例如:‘items’,‘keys’,‘values’
- 元素对象:
签名属性的参数中的元素:有序字典中的value的常用属性
例如:‘annotation’,‘default’,‘empty’,‘kind’,‘name’,‘replace’
如果元素没有注释或者没有default默认值时,该属性就为empty
元素对象属性为字典值的属性
案例:
- 先查看位置参数的注释,必须通过字典中key的值得到注释类型
- 是字典值表现形式\<\Parameter “x:int”>,要进步通过查看他的\<\Parameter “x:int”>.annotation才能得到 class int整正的数据类型
- 在参数核验时,如isinstance(3,int) 中int 就是class int 只有这样才能对比,而\<\Parameter “x:int”>在正常情况下是无法使用的,必须通过annotation将其类型解放出来才能使用
举例:
1 | import inspect |
[<Parameter "x: int">, <Parameter "y: int = 6">, <Parameter "*args">, <Parameter "**kwargs">]
<class 'int'>
<class 'int'>
<class 'inspect._empty'>
<class 'inspect._empty'>
signature获取签名
inspect.signature(callable),获取签名(函数签名中包含了一个函数信息,包括函数名、函数参数、函数所在的类和命名空间及其他信息)
举例:
1 | import inspect |
(x: int, y: int = 6, *args, **kwargs) -> int
OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int = 6">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
<class 'int'>
----------------------------------------
y: int = 6
<class 'int'>
6
========================================
x: int
<class 'int'>
<class 'inspect._empty'>
----------------------------------------
*args
<class 'inspect._empty'>
<class 'inspect._empty'>
========================================
**kwargs
<class 'inspect._empty'>
<class 'inspect._empty'>
对象的判断
- inspect.isfunction(add) 是否是函数
- inspect.ismethod(add) 是否是类的方法
- inspect.isgenerator(add) 是否是生成器对象
- inspect.isgenerafunction(add) 是否是生成器函数
- inspect.isclass(add) 是否是类
- inspect.ismodule(inspect) 是否是模块
- inspect.isbuiltin(print) 是否是内建对象
parameter参数对象
- 保存在元组中,是只读属性
- name,参数的名字
- annotation,参数的注解,可以不定义
- default,参数的缺省值,可以不定义
- empty,特殊的类,用来标识default 或者注释annotation属性为空
- kind,实参如何绑定到形参,就是形参的类型
@ POSITIONAL_ONLY 值必须由位置参数提供
@ POSITIONAL_KEYWORD 值可以作为关键字或者位置参数提供
@ VAR_POSITIONAL 可变位置参数,相当于 args
@ KEYWORD_ONLY ,keyword-only参数,相当于 或者 args 后出现的非可变关键参数
@ VAR_KEYWORD, 可变关键参数,相当于 * kwargs
案例操作:
1 | import inspect |
OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y: int = 7">), ('args', <Parameter "*args">), ('z', <Parameter "z">), ('t', <Parameter "t=10">), ('kwargs', <Parameter "**kwargs">)])
1 x <class 'inspect._empty'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************
2 y <class 'int'> POSITIONAL_OR_KEYWORD 7
7 <class 'inspect._empty'>
**************************************************
3 args <class 'inspect._empty'> VAR_POSITIONAL <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************
4 z <class 'inspect._empty'> KEYWORD_ONLY <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************
5 t <class 'inspect._empty'> KEYWORD_ONLY 10
10 <class 'inspect._empty'>
**************************************************
6 kwargs <class 'inspect._empty'> VAR_KEYWORD <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************
传参检查
需求: 请检查用户输入参数是否符合参数注解的要求1
2def add(x,y:int=7)->:
return x + y
解题思路:
可以通过ordereddict.annotation获取参数的注解类型,再通过isinstance(值,类型)对比
解决方法:
1 | import inspect |
[<Parameter "x: int">, <Parameter "y: int = 6">, <Parameter "*args">, <Parameter "**kwargs">]
OK
OK
7
总结:
获取具体某参数的注释:
方法1. 通过字典值获取参数类型1
2
3Values = list(inpect,signature.paramters)
For v in Values:
print(v.annotation)
方法2:通过字典key获取参数类型1
print(dict[k].annotation)