一.函数定义的弊端
1.python是动态语言,变量随时可以被赋值,且能赋值为不同的类型
2.python不是静态编译型语言,变量类型是在运行器决定的
3.动态语言很灵活,但是这种特性也是弊端
难发现:由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问题
难使用:函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类型的数据
4.如果解决这种动态语言定义的弊端呢?
方式一:增加文档Documentation String
-这只是一个惯例,不上强制标准,不能要求程序员一定为函数提供说明文档
-函数定义更新,文档未必同步更新
方式二:函数注解Function Annotations
<1>python3.5引入
<2>对函数的参数进行类型注解
<3>对函数的返回值进行类型注解
<4>只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
<5>提供给第三方工具,做代码分析,发现隐藏bug
<6>函数注解的信息,保存在__annotations__属性中
def add(x:int,y:int) -> int: #使用参数注释提示描述 return x + y print(add(4,5)) #返回:9
二.业务应用
1.函数参数类型检查
2.思路:
<1>函数参数的检查,一定是在函数外
<2>函数应该作为参数,传入到检查函数中
<3>检查函数拿到函数传入的实际参数,与形参声明对比
<4>__annotations__属性是一个字典,其中包含返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用inspect模块
3.inspect模块
(1)提供获取对象信息的函数,可以检查函数和类,类型检查
4.signature(callable),获取签名(函数签名包含了一个函数的信息,包含函数名,它的参数类型,它所在的类和名称空间及其他信息)
import inspect def add(x:int,y:int,*args,**kwargs) -> int: return x + y sig = inspect.signature(add) #获取函数签名 print(sig) #打印函数签名:(x:int, y:int, *args, **kwargs) -> int print('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['y']) #打印y参数和注解:y:int print(sig.parameters['x'].annotation) #打印x参数的注解:<class 'int'> print(sig.parameters['args']) #打印*args参数:*args print(sig.parameters['args'].annotation) #打印*args参数的注解:<class 'inspect._empty'> print(sig.parameters['kwargs']) #打印**kwargs参数:**kwargs print(sig.parameters['kwargs'].annotation) #打印*args参数的注解:<class 'inspect._empty'>
<1>inspect.isfunction(add),是否是函数
<2>inspect.ismethod(add),是否是类的方法
<3>inspect.isgenerator(add),是否是生成器
<4>inspect.isgeneratorfunction(add),是否是生成器函数
<5>inspect.isclass(add),是否是类
<6>inspect.ismodule(inspect),是否是模块
<7>inspect.isbuiltin(print),是否是内建对象
5.Parameter对象(参数对象)
(1)保存在元祖中,是只读的
(2)nmae,参数的名字
(3)annotation,参数的注解,可能没有定义
(4)default,参数的缺省值,可能没有定义
(5)empty,特殊的类,用来标记default属性或者注释annotation属性的空值
(6)kind,实参如何绑定到形参,就是形参的类型
<1>POSITIONAL_ONLY,值必须是位置参数提供
<2>POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供
<3>VAR_POSITIONAL,可变位置参数,对应*args
<4>KEYWORD_ONLY,keyword-only参数,对应*或者*args之后的出现的非可变关键字参数
<5>VAR_KEYWORD,可变关键字参数,对应**kwargs
6.举例:
import inspect def add(x,y:int=7 , *args, z , t=10 , **kwargs) -> int: return x + y sig = inspect.signature(add) #获取函数签名 print(sig) #打印函数签名:(x, y:int=7, *args, z, t=10, **kwargs) -> int print('params :',sig.parameters) #打印签名的参数(有序字典):params : OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">), ('args', <Parameter "*args">), ('z', <Parameter "z">), ('t', <Parameter "t=10">), ('kwargs', <Parameter "**kwargs">)]) print('return :',sig.return_annotation) #打印返回值注解是什么:return : <class 'int'> print('~~~~~~~~~~~~~~~~~~~~~~~~~~~') for i, item in enumerate(sig.parameters.items()): name,param = item print(i+1,'参数名字:',name,'参数对象注解:',param.annotation,'参数形参类型:',param.kind,'参数缺省值:',param.default) print('缺省值是否为空',param.default is param.empty, end='\n\n') #返回内容: (x, y:int=7, *args, z, t=10, **kwargs) -> int params : OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">), ('args', <Parameter "*args">), ('z', <Parameter "z">), ('t', <Parameter "t=10">), ('kwargs', <Parameter "**kwargs">)]) return : <class 'int'> ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 #装饰器 def check(fn): def wrapper(*args, **kwargs): #实参检查 print(args,kwargs) sig = inspect.signature(fn) #获取函数签名 #print(sig) #打印函数签名:(x:int, y:int=7) -> int #print('params :',sig.parameters) #打印签名的参数(有序字典):params : OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int=7">)]) #print('return :', sig.return_annotation) # 打印返回值注解是什么:return : <class 'inspect._empty'> #print('~~~~~~~~~~~~~~~~~~~~~~~~~~~') params = sig.parameters #通过函数签名获取有序字典 # for name, param in sig.parameters.items(): #name拿到的是形参名字,param拿到的是形参封装的对象 # print(name,param) # # print( '参数名字:', name, '参数对象注解:', param.annotation, '参数形参类型:', param.kind, '参数缺省值:', param.default) # print('缺省值是否为空', param.default is param.empty, end='\n\n') #位置参数处理 params_list = list(params.keys()) #tmp_list = [0]*len(params) #临时列表用来统计params长度 #print(tmp_list) for i,v in enumerate(args): #i是给传进来的位置参数给个序号0,v是获取到实参v=4 k = params_list[i] #借助args索引通过序号找到对应key=x if isinstance(v, params[k].annotation): # 由这个key找到有序字典中对应的定义,找到定义对这个值v=4做类型判断 print(v, 'is', params[k].annotation) else: errstr = "{} {} {}".format((v, 'is not', 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: errstr = "{} {} {}".format((v, 'is not', 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 print('位置参数类型检查:') print(add(4,8)) print('关键字参数类型检查:') print(add(x=4,y=8)) #类型错误参数 #print(add('xixi','y=dongdong')) #报错 #返回结果: 位置参数检查: (4, 8) {} [0, 0] 4 is <class 'int'> 8 is <class 'int'> 12 关键字参数检查: () {'x': 4, 'y': 8} [0, 0] 4 is <class 'int'> 8 is <class 'int'> 12
三.functools模块
1.partial方法
(1)偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回
(2)从partial生成的新函数,是对原函数的封装
(3)partial方法举例
举例1:
import functools def add(x,y:int)->int: ret = x + y print(ret) return ret #import inspect #print(inspect.signature(add)) newadd= functools.partial(add,4) #把函数固定参数固定下来 newadd(5) #返回:9
举例2:
import functools def add(x,y,*args)->int: print(args,'\t\t\t',x+y) return x + y newadd= functools.partial(add,1,3,6,5) #把函数固定参数固定下来(1传x,3传y,6,5传*args) newadd(7) #把7传*args import inspect print(inspect.signature(add)) #原来的签名 print(inspect.signature(newadd)) #新的签名 #返回: (6, 5, 7) 4 (x, y, *args) -> int (*args) -> int
2.lru_cache装饰器
(1)@functools.lru_cache(maxsize=128,typed=False)
<1>Least-recently-used装饰器。lru,最近最少使用。cache缓存
<2>如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二幂时,LRU功能执行得最好
<3>如果typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被是为居右不同结果的不同调用
(2)lru_cache装饰器:通过一个字典缓存被装饰函数的调用和返回值
import functools import time @functools.lru_cache() def add(x,y=5): time.sleep(3) ret = x + y print(ret) return ret add(4,5) #第一次访问3秒后返回: 9 #第二次访问被缓存瞬间返回: 9
(3)lru_cache装饰器应用
<1>使用前提
同样的函数参数一定得到同样的结果
函数执行时间很长,且亚奥多次执行
<2>本质是函数调用的参数=>返回值
<3>缺点:
不支持缓存过期,key无法过期,失效
不支持清除操作
不支持分布式,是一个单机的缓存
<4>适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询