python类型注解
函数定义的弊端:
python是动态语言,变量随时可以被赋值,且能赋值为不同的类型;
python不是静态编译型语言,变量类型是在运行时决定的;
动态语言很灵活,但这种特性也是弊端:
难发现,由于不做任何类型检查,直到运行时问题才显现出来,或在线上才能暴露出问题;
难使用,函数的使用者看到函数时,并不知道设计者是如何设计的函数,也不知道应该传入什么类型的数据;
例:
In [63]: add(4,5)
Out[63]: 9
In [64]: add('hello','world')
Out[64]: 'helloworld'
In [65]: add(4,'hello') #强弱类型语言的区别举例
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-65-d5d4453c2cd4> in <module>()
----> 1 add(4,'hello')
<ipython-input-62-c1dcfb42218b> in add(x, y)
1 def add(x,y):
----> 2 return x+y
TypeError: unsupported operand type(s) for +: 'int' and 'str'
解决函数定义的弊端:
Documentation String;
这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档;
函数定义更新了,文档未必同步更新;
例:
In [67]: def add(x,y):
...: '''
...: :param x: int
...: :param y: int
...: :return: int
...: '''
...: return x+y
...:
In [68]: help(add)
Help on function add in module __main__:
add(x, y)
:param x: int
:param y: int
:return: int
(END)
function annotation函数注解:
与java注解是两码事;
python3.5引入;
对函数的参数进行类型注解;
对函数的返回值进行类型注解;
只对函数参数作一个辅助说明,并不对函数参数进行类型检查;
提供给第三方工具,作代码分析,发现隐藏的bug;
函数注解的信息保存在__annotations__属性中,如add.__annotations__;
变量注解:
python3.6引入;
业务应用:
函数参数类型检查;
思路:
函数参数的检查,一定是在函数外;
函数应该作为参数,传入到检查函数中(装饰器);
检查函数拿到函数传入的实际参数,与形参声明对比;
__annotations__属性是一个字典,包括函数参数及返回值的声明,是普通字典(非有序字典),假设要做位置参数的判断,无法和此字典中的声明对应,要使用inspect模块;
例:
In [71]: def add(x:int,y:int)->int:
...: '''
...: :param x: int
...: :param y: int
...: :return: int
...: '''
...: return x+y
...:
In [73]: add.__annotations__ #普通字典,而非有序字典
Out[73]: {'return': int, 'x': int, 'y': int}
inspect模块:
提供获取对象信息的函数,可以检查函数和类、类型检查;
inspect.signature(callable),获取签名,函数签名包含了一个函数的信息,包括函数名、函数参数、缺省值、返回值,它的参数类型,它所在的类,和名称空间及其它信息;
inspect.isfunction(add),是否是函数,限定只是函数,函数在类中为method;
inspect.ismethod(add),是否是类的方法;
inspect.isgenerator(add),是否是生成器对象;
inspect.isgeneratorfunction(add),是否是生成器函数;
inspect.isclass(add),是否是类;
inspect.ismodule(inspect),是否是模块;
inspect.isbuiltin(print),是否是内建对象;
例:
In [74]: import inspect
In [75]: def add(x:int,y:int,*args,**kwargs)->int:
...: return x+y
...:
In [76]: add.__annotations__ #是普通字典,顺序随机
Out[76]: {'return': int, 'x': int, 'y': int}
In [77]: sig=inspect.signature(add)
In [78]: sig #函数签名,声明是什么样,即函数第一行,定义时的东西
Out[78]: <Signature (x:int, y:int, *args, **kwargs) -> int>
In [79]: print('params:',sig.parameters) #OrderedDict有序字典,解决了调用时传参的顺序问题,可迭代,迭代中的每一个元素为parameter
params: OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
In [80]: print('return:',sig.return_annotation)
return: <class 'int'>
In [81]: sig.parameters['x']
Out[81]: <Parameter "x:int">
In [82]: sig.parameters['x'].annotation
Out[82]: int
In [83]: sig.parameters['y']
Out[83]: <Parameter "y:int">
In [84]: sig.parameters['y'].annotation
Out[84]: int
In [85]: sig.parameters['args']
Out[85]: <Parameter "*args">
In [86]: sig.parameters['args'].annotation
Out[86]: inspect._empty
In [87]: sig.parameters['kwargs']
Out[87]: <Parameter "**kwargs">
In [88]: sig.parameters['kwargs'].annotation
Out[88]: inspect._empty
parameter对象:
保存在元组中,是只读的;
name,参数的名字;
default,参数的缺省值,可能没有定义;
annotation,参数的注解,可能没有定义;
empty,特殊的类,用来标记default属性或annotation属性的空值,与sig.parameters['x'].annotation是一个东西;
kind,实参如何绑定到形参,就是形参的类型:
POSITIONAL_ONLY,值必须是位置参数提供,python中未实现此项,仅常量定义了;
POSITIONAL_OR_KEYWORD,值可以作为关键字或位置参数提供;
VAR_POSITIONAL,可变位置参数,对应*args;
KEYWORD_ONLY,keyword-only参数,对应*或*args之后出现的非可变关键字参数;
VAR_KEYWORD,可变关键字参数,对应**kwargs;
POSITIONAL_OR_KEYWORD,VAR_POSITIONAL,KEYWORD_ONLY,VAR_KEYWORD,参数类型(形参)可用此判断;
实参的数据类型用annotation判断;
例:
In [93]: def add(x,y:int=7,*args,z,t=10,**kwargs)->int:
...: return x+y
...:
In [94]: sig=inspect.signature(add)
In [95]: sig
Out[95]: <Signature (x, y:int=7, *args, z, t=10, **kwargs) -> int>
In [96]: sig.parameters
Out[96]:
mappingproxy({'args': <Parameter "*args">,
'kwargs': <Parameter "**kwargs">,
't': <Parameter "t=10">,
'x': <Parameter "x">,
'y': <Parameter "y:int=7">,
'z': <Parameter "z">})
In [97]: sig.return_annotation
Out[97]: int
In [98]: print(sig.return_annotation)
<class 'int'>
In [99]: for i,(name,param) in enumerate(sig.parameters.items()):
...: print(i+1,name,param.annotation,param.kind,param.default)
...: print(param.default is param.empty,end='\n\n')
...:
1 x <class 'inspect._empty'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
True
2 y <class 'int'> POSITIONAL_OR_KEYWORD 7
False
3 args <class 'inspect._empty'> VAR_POSITIONAL <class 'inspect._empty'>
True
4 z <class 'inspect._empty'> KEYWORD_ONLY <class 'inspect._empty'>
True
5 t <class 'inspect._empty'> KEYWORD_ONLY 10
False
6 kwargs <class 'inspect._empty'> VAR_KEYWORD <class 'inspect._empty'>
True
例(参数检查):
import inspect
from functools import wraps
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print(args,kwargs) #此处不可以**kwargs,print函数中没有类似y=7关键字参数
sig = inspect.signature(fn)
print(sig)
print('params:',sig.parameters)
print('return:',sig.return_annotation)
print('~~~~~~~~~~~~~~~~~~~~~~~')
# for i,(name,param) in enumerate(sig.parameters.items()):
# print(i+1,name,param.name,param.annotation,param.kind,param.default)
# print(param.default is param.empty,end='\n\n')
# for param in sig.parameters.values():
# print(param.name,param)
# print(param.name,param.annotation,param.kind,param.default)
params = sig.parameters
param_list = list(params.keys())
for i,v in enumerate(args): #位置参数传参处理
k = param_list[i] #用key找key,技巧
if isinstance(v,params[k].annotation):
print(v,'is',params[k].annotation)
else:
# print(v,'is not',params[k].annotation)
errstr = '{} is not {}'.format(v,params[k].annotation)
print(errstr)
raise TypeError(errstr)
for k,v in kwargs.items(): #关键字参数传参处理
if isinstance(v,params[k].annotation):
print(v,'is',params[k].annotation)
else:
# print(v,'is not',params[k].annotation)
errstr = '{} is not {}'.format(v,params[k].annotation)
print(errstr)
raise TypeError(errstr)
ret = fn(*args,**kwargs)
return ret
return wrapper
@check
def add(x:int,y:int=7)->int:
return x + y
#add(4,8)
#add(x=4,y=8)
#add(4,y=8)
#add('mag','edu')
add(x='mag',y='edu')
#add(4)
#add(4,8,y=8)
注:
MappingProxyType,有序字典被包装过(虚的,假的);
视图,一般只读;
pycharm里抽取函数,选中内容-->Refactor-->Extract-->Method
例:
from functools import wraps
import inspect
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i,p in enumerate(args):
if isinstance(p,values[i].annotation):
print('==')
for k,v in kwargs.items():
if isinstance(v,params[k].annotation):
print('===')
return fn(*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=7):
return x + y
#add(4,8)
#add(x=4,y=8)
#add(4,y=2)
add(4)
####################
from functools import wraps
import inspect
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i,p in enumerate(args):
# if isinstance(p,values[i].annotation):
# print('==')
param = values[i]
if param.annotation is not param.empty and not isinstance(p,param.annotation):
print(p,'!=',values[i].annotation)
for k,v in kwargs.items():
# if isinstance(v,params[k].annotation):
# print('===')
if params[k].annotation is not inspect._empty and not isinstance(v,params[k].annotation):
print(v,'!==',params[k].annotation)
return fn(*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=7):
return x + y
#add(4,8)
#add(x=4,y=8)
#add(4,y=2)
#add(4)
add('mag','edu')
注:
param.empty与inspect._empty一样;