函数注解
函数定义弊端
- python是动态语言,变量随时可以被赋值,且能赋值为不同的类型
- python不是静态编译型语言,变量类型是在运行期决定的
- 动态语言很灵活,但是这种特性也是弊端
def add(x,y):
return
print(add(4,5))
print(add("hello","world"))
add(4,'hello')
问题:
(1)难发现:由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问题
(2)难使用:函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类型的数据
弊端解决途径
文档注释
- 增加文档Documentation String
- 这只是一个惯例,不是强制标准,不能要求程序员一定要为函数提供说明文档
- 函数定义更新了,文档未必同步更新
def add(x,y):
"""
:param x:int
:param y:int
:return int
"""
return x + y
print(help(add))
运行结果:
add(x, y)
:param x:int
:param y:int
:return int
None
5
函数Annotations
如何解决这种动态语言定义的弊端呢?
annotation[ænə’teɪʃn] 注释、注解
通常function Annotations就是函数注解
def add(x:int,y:int)->int: #函数参数、函数返回值进行注解
"""
:param x:int
:param y:int
:return:int
"""
return x+y
print(help(add))
print("~~~~~~~~~~~~~~~~~~~~~~~")
print(add(4,5))
print("~~~~~~~~~~~~~~~~~~~~~~~")
print(dir(add))
运行结果:
Help on function add in module __main__:
add(x:int, y:int) -> int
:param x:int
:param y:int
:return int
None
~~~~~~~~~~~~~~~~~~~~~~~
5
~~~~~~~~~~~~~~~~~~~~~~~
['__annotations__', '__call__', '__class__', '__closure__',
'__code__', '__defaults__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__',
'__getattribute__', '__globals__', '__gt__', '__hash__', '__init__',
'__kwdefaults__', '__le__', '__lt__', '__module__', '__name__',
'__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__']
函数注解属性保存
由上可知注解都是保存在函数__annotations__之中的,函数注解到底是怎么回事呢?
- python 3.5引入函数注解
- 对函数的参数进行类型注解
- 对函数的返回值进行类型注解
- 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
- 提供给第三方工具,做代码分析,发现隐藏的bug
- 函数注解的信息,保存在__annotations__属性中
- 根据注解形式即可知道存储在字典之中的
def add(x:int,y:int)->int:
"""
:param x:int
:param y:int
:return int
"""
return x+y
print(add.__annotations__)
运行结果:
{'return': <class 'int'>, 'x': <class 'int'>, 'y': <class 'int'>}
参数类型检查应用
函数参数类型检查思路:
- 函数参数的检查,一定在函数外
- 函数应该作为参数,传入到检查函数中[装饰器]
- 检查函数拿到函数传入的实际参数,与形参声明对比
__annotations__属性是一个字典,其中包括返回值类型的声明,假设要做位置参数的判断,无法和字典声明对应,使用inspect模块
inspect模块提供获取对象信息的函数,可以坚持函数和类、类型检查
inspect模块参数检查
参数检查对象结构
在使用inspect模块检查参数时,首先要对函数签名、签名的参数,参数中的元素等要有清晰的认知,从架构上把控他们之间的关系,具体可参考下图:
元素对象属性为字典值得属性
案例:
- 想查看位置参数的注释,必须要通过字典中key的值得注释类型,
- 是字典值表现形式\<\Parameter “x:int”>,要进步通过查看他的\<\Parameter “x:int”>.annotation才能得到 class int整正的数据类型
- 在参数核验时,如isinstance(3,int)其中int就是class int 只有这样才能对比,而\<\Parameter “x:int”>在正常情况下是无法使用的,必须通过annotation将其类型解放出来才能使用
import inspect
def add(x:int,y:int=7,*args,**kwargs)->int:
return x + y
sig = inspect.signature(add)
params = sig.parameters
values = [x for x in params.values()]
print(values)
for i,v in enumerate(values):
print(values[i].annotation)
运行结果:
signature获取签名
inspect.signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)
import inspect
def add(x:int,y:int=7,*args,**kwargs)->int:
return x + y
sig = inspect.signature(add)
params = sig.parameters
print(sig)
print(params)
print(sig.return_annotation)
print('~~~~~~~~~~~~~~~~~~~~~~~~~')
print(params['y'])
print(params['y'].annotation)
print(params['y'].default)
print('~~~~~~~~~~~~~~~~~~~~~~~~~')
print(params['x'])
print(params['x'].annotation)
print(params['x'].default)
print('~~~~~~~~~~~~~~~~~~~~~~~~~')
print(params['args'])
print(params['args'].annotation)
print(params['args'].default)
print('~~~~~~~~~~~~~~~~~~~~~~~~~')
print(params['kwargs'])
print(params['kwargs'].annotation)
print(params['kwargs'].default)
运行结果:
(x:int, y:int=7, *args, **kwargs) -> int
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int=7">),
('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
<class 'int'>
~~~~~~~~~~~~~~~~~~~~~~~~~
y:int=7
<class 'int'>
7
~~~~~~~~~~~~~~~~~~~~~~~~~
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_OR_KEYWORD,值可以作为关键字或者位置参数提供
@VAR_POSITIONAL,可变位置参数,对于*args
@KEYWORD_ONLY,keyword-only参数,对应于*或者*args之后的出现非可变关键字参数
@VAR_KEYWORD,可变关键字参数,对应**kwargs
案例操作
import inspect
def add(x,y:int=7,*args,z,t=10,**kwargs)->int:
return x +y
sig = inspect.signature(add)
parames = sig.parameters
print(parames)
for i,val in enumerate(parames.items()): #通过迭代有序字典获取value
name,param = val
print(i+1,name,param.annotation,param.kind,param.default)
print(param.default is param.empty)
运行结果:
1 x <class 'inspect._empty'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
True
2 y <class 'int'> POSITIONAL_OR_KEYWORD 7
False
......
传参核验应用
需求
有函数如下:
def add(x,y:int=7)->int:
return x + y
请检查用户输入是否符合参数注解的要求
解题思路
解决方法
import inspect
def checkparams(fn):
def wapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())**#字典的values,为的是通过values.annotation解放注释类型**
print(values)
for i,p in enumerate(args):
param = values[i]
if param.annotation is not param.empty and isinstance(p,values[i].annotation):
print('OKKKK')
for k,v in kwargs.items():#**获取数据类型的第二种手段,直接通过**dict[key].annotation等价于values.annotation
if params[k].annotation is not params[k].empty and isinstance(v,params[k].annotation):
print("OK")
func = fn(*args,**kwargs)
return func
return wapper
@checkparams
def add(x,y:int=7)->int:
return x+y
print(add(3,y=4))