Python类型注解
注解-annotation
Python是动态强类型语言,变量可以随时被赋值并改变类型,也就是说Python的变量是运行时决定的
def add(x,y): ''' This is a add function x is int y is int ''' return x + y add(4,5) add(4.0,5) add([1,2],[3,4]) add('abc',4)#不到运行时是无法判断类型是否正确
动态语言的弊端:
难发现,由于不能做任何类型的检查,往往到了运行时出问题才显现出来,或到了运行时才暴露出来
难使用,函数使用者看到函数时,并不知道设计者的意图,如果没有详细的文档,使用者只能是猜测数据的类型,对于一个新的API,使用者往往不知道该怎么使用,对于返回的类型更是不知道该怎么使用
动态类型对类型的约束不强,在小规模的开发危害不大,但是随着Python的广泛使用,这种缺点确实对大项目的开发危害非常大
如何解决这种问题?
文档字符串:对函数,类、模块使用尽可能详细的使用描述、举例,让使用者使用帮助文档就能知道使用方式,但是大多数项目管理不严格,可能导致文档不全,或项目发生变更后文档未更新等
类型注解:函数注解、变量注解
函数注解
字符串文档在编码的过程冲未必同步更新,只是对函数的形参类型、返回值类型的辅助说明,非强制类型约束
def add(x:int,y:int) -> int: #x:int形参注解,-> int 返回值注解 ''' This is a add function :param x: :param y: :return: ''' return x + y print(help(add))#获取帮助文档 print(add(4,y=5))
函数注解:
Python3.5引入
对函数的形参和返回值的类型进行说明
只是对函数的形参类型、返回值类型的辅助说明,非强制类型约束
第三方工具例如pycharm就可以根据类型注解进行代码分析,发现隐藏bug
函数注解放在了函数的属性__annotations__中,在有序字典中
print(add.__annotations__) {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>} #在Python3.6以后是记录了录入序,但字典实际的无序的
变量注解:
i:int = 3 j:str = 'abc' k:str = 100 print(i,j,k) #输出 3 abc 100
3.6版本引入,对变量类型的说明,非强制约束
第三方工具可以进行类型分析和推断
业务应用,类型检查
函数传参经常传错,如何检查?
可以在函数内部写isinstance来判断参数类型是否正确,但是检查可以看做不是业务代码,写在里面就是侵入式代码;
使用装饰器:
非侵入式代码
动态获取待检查的函数和参数类型注解
当函数调用传入实参时,和类型注解对比
inspect模块
inspect模块可以获取Python中各种对象信息
import inspect #导入模块 inspect.isfunction(add) #判断是否是函数 inspect.ismethod(add) #是否是方法 inspect.isgenerator(add) #是否是生成器 inspect.isgeneratorfunction(add) #是否是生成器函数 inspect.isclass(add) #是否是类 inspect.isbuiltin(add) #是否是内嵌函数
......
函数签名:inspect.signature() signature(callable) 获取签名(函数签名包含了一个函数的信息,包括函数名,参数类型,它所在的类和命名空间及其他信息)
parameter对象:
name,参数名
default,缺省值
annotation,类型注解
kind,类型:
POSITION_ONLY 值必须是位置参数,在Python3.8后支持
POSITION_OR_KEYWORD 值可以作为关键字或者位置参数提供
VAR_POSITIONAL 可变位置参数,对应*args
KEYWORD_ONLY 对应* 或者*args之后出现的非可变关键字参数
VAR_KEYWORD 可变关键字参数,对应**kwargs
import inspect def add(x:int,y:int) -> int: #分别注明了参数注解和返回值注解都为int类型 return x + y print(add.__name__,add.__annotations__,add.__defaults__) #返回函数名,参数注解,参数默认值 #add {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>} None print(inspect.isfunction(add)) #判断是否为函数,True sig = inspect.signature(add) print(sig) # (x:int, y:int) -> int 函数签名 print(sig.return_annotation) #<class 'int'> 获取返回值注解 parameters = sig.parameters #获取参数,以及参数类型注解 print(parameters) #OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)]) #有序字典 for k,v in parameters.items(): print(type(k),type(v),k,v,sep='\t') #输出 # <class 'str'> <class 'inspect.Parameter'> x x:int # <class 'str'> <class 'inspect.Parameter'> y y:int for _,v in parameters.items(): print(v.name,v.annotation,v.default,v.empty,v.kind,sep='+++') #输出 x+++<class 'int'>+++<class 'inspect._empty'>+++<class 'inspect._empty'>+++POSITIONAL_OR_KEYWORD y+++<class 'int'>+++<class 'inspect._empty'>+++<class 'inspect._empty'>+++POSITIONAL_OR_KEYWORD
#inspect.signature(add).parameters.items()
有如下函数:
请检查用户的输入是否符合参数类型注解的要求
分析:
调用时,用户输入才会传入实参,才能判断实参是否符合类型要求
调用时,让用户感觉上还是调用原来的函数,使用装饰器实现
如果类型不对,则提醒用户
def add(x:int,y:int=7) -> int: ''' This is a add function x is int y is int ''' return x + y add(4,5) add('abc','de')
NO1:先实现对add函数的参数类型的提取
#先实现对add函数的参数类型的提取 import inspect def check(fn): sig = inspect.signature(fn) parameters = sig.parameters for k,v in parameters.items(): print(k,v.annotation,v.default,v.name,v.kind) def add(x:int,y:int=7) -> int: ''' This is a add function x is int y is int ''' return x + y
print(check(add))
add(4,5)
改造成使用装饰器
import inspect def check(fn): def wrapper(*args,**kwargs): sig = inspect.signature(fn) parameters = sig.parameters print(sig,parameters) for k,v in parameters.items(): print(k,v.annotation,v.default,v.name,v.kind) ret = fn(*args,**kwargs) return ret return wrapper @check def add(x:int,y:int=7) -> int: ''' This is a add function x is int y is int ''' return x + y add(4,5)
解决按位置传参
import inspect def check(fn): def wrapper(*args,**kwargs): sig = inspect.signature(fn) parameters = sig.parameters print(sig,parameters.values()) values = list(parameters.values()) print(values[0]) print(values[0].annotation) for i,x in enumerate(args): if values[i].annotation is not values[i].empty and isinstance(x,values[i].annotation): print("{}={}".format(values[i].name,x),'is ok') else: print("{}={}".format(values[i].name,x),'is error') ret = fn(*args,**kwargs) return ret return wrapper @check def add(x:int,y:int=7) -> int: ''' This is a add function x is int y is int ''' return x + y add(4)
解决关键字传参
import inspect def check(fn): def wrapper(*args,**kwargs): sig = inspect.signature(fn) parameters = sig.parameters values = list(parameters.values()) for i,x in enumerate(args): if values[i].annotation is not values[i].empty and isinstance(x,values[i].annotation): print("{}={}".format(values[i].name,x),'is ok') else: print("{}={}".format(values[i].name,x),'is error') for k,v in kwargs.items(): if isinstance(v,parameters[k].annotation)and parameters[k].annotation is not inspect._empty: print("{}={}".format(parameters[k].name,v),'is ok') else: print("{}={}".format(parameters[k].name,v),'is error') ret = fn(*args,**kwargs) return ret return wrapper @check def add(x:int,y:int=7) -> int: ''' This is a add function x is int y is int ''' return x + y add(4,y=5)
#解决按关键字传参 import inspect import functools def check(fn): @functools.wraps(fn) def wrapper(*args,**kwargs): sig = inspect.signature(fn) parameters = sig.parameters values = list(parameters.values()) for i,x in enumerate(args): if values[i].annotation is values[i].empty and not isinstance(x,values[i].annotation): print("{}={}".format(values[i].name,x),'is error') for k,v in kwargs.items(): if not isinstance(v,parameters[k].annotation) and parameters[k].empty is inspect._empty: print("{}={}".format(k,v),'is error') ret = fn(*args,**kwargs) return ret return wrapper @check def add(x:int,y:int=7) -> int: ''' This is a add function x is int y is int ''' return x + y print(add(4,y=5)) print(add.__name__,add.__doc__)
import time import datetime import inspect from functools import wraps def logger(t=1): def _logger(fn): @wraps(fn) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() if delta > t: print('The function {} tooks {}s'.format(fn.__name__,delta)) return ret return wrapper return _logger def check(fn): def wrapper(*args,**kwargs): sig = inspect.signature(fn) parmeters = sig.parameters values = list(parmeters.values()) #解决按位置传参 for i,x in enumerate(args): if isinstance(x,values[i].annotation) and values[i].annotation is not values[i].empty: print("{}={} is ok".format(values[i].name,x)) else: print("{}={} is error".format(values[i].name,x)) #解决按关键字传参 for k,v in kwargs.items(): if isinstance(v,parmeters[k].annotation) and parmeters[k].annotation is not inspect._empty: print("{}={} is ok".format(parmeters[k].name,v)) else: print("{}={} is error".format(parmeters[k].name,v)) ret = fn(*args,**kwargs) return ret return wrapper @logger() @check def add(x,y:int) ->int: ''' This is a add function :param x:int :param y:int :return:int ''' time.sleep(2) return x + y add(4,y=5) #输出 x = 4 is error y = 5 is ok

浙公网安备 33010602011771号