一.函数定义的弊端
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>适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询