python类型注解及functools中的其他方法
- python函数定义的弊端
- 函数注解:Function Annotations
- 函数注解的作用
- 业务应用
- inspect模块
- is判断系列
- 返回的parameters对象
- 代码示例
- 将获取数据的功能改造成装饰器
- functools中的其他方法
- partial 方法
- lru_cache方法
- 斐波那契数列的改造
- lru_cache方法的应用
- 缓存的简单介绍
python函数定义的弊端
- python是动态语言,变量随时可以被复制后,且能够赋值为不同的类型
- python不是静态编译型语言,变量类型是在运行器上决定的
- 动态语言很灵活,但是这种特性也是弊端
- 弊端一:难发现。由于不做任何类型的检查,直到运行问题才会显示出现,或者线上运行时才会爆喽问题
- 弊端二:难使用,函数的使用者看到函数的时候,并不知道你函数的设计,并不知道应该传入什么类型的数据
函数注解:Function Annotations
- 增加文档:Documentation String
- 这只是一个惯例,并不是强制的标准,不能要求程序员员一定为函数提供说明文档
- 函数定义更新了,文档未必同步更新
函数注解的作用
- python 3.5开始引入
- 对函数的返回值进行类型注解
- 对函数的参数进行类型注解
- 只对函数参数做一个辅助说明,并不对函数参数进行类型检查
- 提供给第三方工具,做代码分析,发现隐藏的bug
- 函数注解的信息,保存在
__annotations__
属性中 - 变量注解从python3.6开始引入
- 下方代码中,
x:int,y:int
告诉使用者,x和y应传int类型,->int
的含义是:如果传参类型正确,返回的也是int类型 -
i:int = 3
是变量注解,告知i是一个int类型
def add(x:int,y:int)->int:
'''
用来写函数的作用
:param x:
:param y:
:return:
'''
i:int = 3
return x+y+i
print(add(4,5))
print("hello","word")
print(add.__annotations__)
=======run_result============
12
hello word
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
业务应用
- 函数参数类型的检查
- 思路:
- 函数参数的检查,一定在函数外
- 函数应该作为参数,传入到检查函数中
- 检查函数拿到函数传入的实际参数,与形参声明对比
-
__annotations__
属性是一个字典,其中包括返回值类型的声明,假设要做位置参数的判断,无法和字典中的声明做队医你过,使用inspect模块 - inspect模块:提供获取对象的函数,可以检查函数和类,类型检查
inspect模块
-
inspect.signature(callable)
1.获取签名(函数签名包含一个函数的信息,包括函数名 参数类型,他所在的类和名称空间及其他信息)
2.add.__annotations__
也能返回函数的属性,这里不用这个属性的原因是:__annotations__
的返回值并不是有序字典,位置不固定,只能拿名称对应,不能拿顺序对应
is判断系列
-
inspect.isfunction(add)
是否是函数 -
inspect.ismethod(add)
是否是类的方法 -
inspect.isgeneratorfunction(add)
是否是生成器函数 -
inspect.isgenerator(add)
是和否是生成器对象 -
inspect.isclass(add)
是否是类 -
inspect.ismodule(add)
是否是模块 -
inspect.isbuiltin(add)
是否是内建对象 - 还有其他的一些类型,python定义的,都可以在这儿找到,请仔细ing学习
返回的parameters对象
- 保存在元组中,是只读的
- name,表示参数的名字
- annotation,表示参数的注解
- default,参数的缺省值,可能没有定义
- empty,特殊的类,用来标记default属性,或者人注释annotation属性的空值
- kind,实参如何绑定形参,就是形参的类型
1.POSITIONAL_OR_KEYWORD,含义是值作为默认参数或者位置参数提供
2.VAR_POSITIONAL,含义是动态参数,对应*args
3.VAR_KEYWORD,含义是关键字参数,对应**kwargs
4.KEYWORD_ONLY,含义是keyword-only参数,对应*
或者*args
之后出现的非关键字参数
5.POSITIONAL_ONLY,含义是值必须是位置参数提供(不常见,可能就没有实现,见到了知道是什么就好)
代码示例
def add(x:int,y:int,z:int=9,*args,c,**kwargs)->int:
'''
用来写函数的作用
:param x:
:param y:
:return:
'''
i:int = 3
return x+y+i+z
import inspect
print(add.__annotations__)
flag= inspect.signature(add)#函数签名
print("***********inspect签名*****************")
print("params的信息:",flag.parameters)#OrderedDict,有序字典
print("return的信息:",flag.return_annotation)
print("x的信息和y的属性:",flag.parameters["x"],flag.parameters["y"].annotation)
print("args的信息和属性:",flag.parameters["args"],flag.parameters["args"].annotation)
print("kwargs的信息和属性:",flag.parameters["kwargs"],flag.parameters["kwargs"].annotation)
print("************is判断系列****************")
print("是否是函数:",inspect.isfunction(add))
print("是否是类的方法:",inspect.ismethod(add))
print("是否是生成器函数:",inspect.isgeneratorfunction(add))
print("是否是生成器对象:",inspect.isgenerator(add))
print("是否是类:",inspect.isclass(add))
print("是否是模块:",inspect.ismodule(add))
print("是否是内建对象:",inspect.isbuiltin(add))
print("************函数返回的parameters对象****************")
print("参数名称:",flag.parameters["x"].name)
print("参数注释:",flag.parameters["x"].annotation)
print("参数缺省值:",flag.parameters["x"].default)
print("参数缺省值:",flag.parameters["z"].default)
print("特殊的类,标记default的属性:",flag.parameters["x"].empty)
print("形参x的类型:",flag.parameters["x"].kind)
print("形参z的类型:",flag.parameters["z"].kind)
print("形参*args的类型:",flag.parameters["args"].kind)
print("形参**kwargs的类型:",flag.parameters["kwargs"].kind)
print("形参c的类型:",flag.parameters["c"].kind)
============run_result===============
{'x': <class 'int'>, 'y': <class 'int'>, 'z': <class 'int'>, 'return': <class 'int'>}
(x: int, y: int, z: int = 9, *args, c, **kwargs) -> int
************inspect签名****************
params的信息: OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int">), ('z', <Parameter "z: int = 9">), ('args', <Parameter "*args">), ('c', <Parameter "c">), ('kwargs', <Parameter "**kwargs">)])
return的信息: <class 'int'>
x的信息和y的属性: x: int <class 'int'>
args的信息和属性: *args <class 'inspect._empty'>
kwargs的信息和属性: **kwargs <class 'inspect._empty'>
************is判断系列****************
是否是函数: True
是否是类的方法: False
是否是生成器函数: False
是否是生成器对象: False
是否是类: False
是否是模块: False
是否是内建对象: False
************函数返回的parameters对象****************
参数名称: x
参数注释: <class 'int'>
参数缺省值: <class 'inspect._empty'>
参数缺省值: 9
特殊的类,标记default的属性: <class 'inspect._empty'>
形参x的类型: POSITIONAL_OR_KEYWORD
形参z的类型: POSITIONAL_OR_KEYWORD
形参*args的类型: VAR_POSITIONAL
形参**kwargs的类型: VAR_KEYWORD
形参c的类型: KEYWORD_ONLY
将获取数据的功能改造成装饰器
import functools
import inspect
def logger(fn):
@functools.wraps(fn)
def logger_1(*args,**kwargs):
#实参检查
print(args,kwargs)#args前可以加*,kwargs前不能加*
#原因:kwargs参数是关键字参数,必须要带变量名,函数中默认值的变量没有定义,他会找不到的
flag= inspect.signature(fn)
print("标签:",flag)
print("param:",flag.parameters)
print("return:",flag.return_annotation)
print("****************分界线****************")
#flag.parameters最终解析的结果是个字典,因此可以使用item方法,
# 因为enumerate返回了索引,因此需要三个变量接收
for i ,(name,value) in enumerate(flag.parameters.items()):
print(i+1,name,value.annotation,value.kind,value.default)
print(value.default is value.empty,end="\n\n")
ret = fn(*args,**kwargs)
return ret
return logger_1
@logger
def add(x:int,y:int,z:int=9,*args,c,**kwargs)->int:
'''
用来写函数的作用
:param x:
:param y:
:return:
'''
i:int = 3
return x+y+i+z
k = add(4,y=7,c=10)
=============run_result===============
(4,) {'y': 7, 'c': 10}
标签: (x: int, y: int, z: int = 9, *args, c, **kwargs) -> int
param: OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int">), ('z', <Parameter "z: int = 9">), ('args', <Parameter "*args">), ('c', <Parameter "c">), ('kwargs', <Parameter "**kwargs">)])
return: <class 'int'>
****************分界线****************
1 x <class 'int'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
True
2 y <class 'int'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
True
3 z <class 'int'> POSITIONAL_OR_KEYWORD 9
False
4 args <class 'inspect._empty'> VAR_POSITIONAL <class 'inspect._empty'>
True
5 c <class 'inspect._empty'> KEYWORD_ONLY <class 'inspect._empty'>
True
6 kwargs <class 'inspect._empty'> VAR_KEYWORD <class 'inspect._empty'>
True
- 客户不需要知道中间校验的判断结果,因此控制台中输出的信息需要控制,不要将校验的结果输出在控制台上
- 代码优化如下:
import inspect
def logger(fn):
def logger_1(*args,**kwargs):
#实参检查
print(args,kwargs)#args前可以加*,kwargs前不能加*
#原因:kwargs参数是关键字参数,必须要带变量名,函数中默认值的变量没有定义,他会找不到的
flag= inspect.signature(fn)
param = flag.parameters
print(param)
print("****************分界线****************")
#关键字传参验证
for k,v in kwargs.items():
if isinstance(v,param[k].annotation):
print(v,"==", param[k].annotation,"result is True")
else:
print(v, "==", param[k].annotation, "result is False")
#位置参数验证
param_list = tuple(param.keys())#将有序字典的key放到列表中
for x,y in enumerate(args):
key = param_list[x]
if isinstance(y,param[key].annotation):
print(y, "==", param[key].annotation, "result is True")
else:
print(y, "==", param[key].annotation, "result is False")
ret = fn(*args,**kwargs)
return ret
return logger_1
@logger
def add(x:int,y:int,z:int=9,*args,c,**kwargs)->int:
'''
用来写函数的作用
:param x:
:param y:
:return:
'''
i:int = 3
return x+y+i+z
print("run_add_result:",add(4,7,c=10,z=99))
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
================run_result==============
(4, 7) {'c': 10, 'z': 99}
OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int">), ('z', <Parameter "z: int = 9">), ('args', <Parameter "*args">), ('c', <Parameter "c">), ('kwargs', <Parameter "**kwargs">)])
****************分界线****************
10 == <class 'inspect._empty'> result is False
99 == <class 'int'> result is True
4 == <class 'int'> result is True
7 == <class 'int'> result is True
run_add_result: 113
name=add
doc=用来写函数的作用
:param x:
:param y:
:return:
functools中的其他方法
partial 方法
- 偏函数,将部分函数的参数固定下来,相当于为部分函数的参数添加了一个固定的默认值,形成一个新的函数并返回
- 从partial生成的新函数,是对原装函数的封装,从下方的代码中可以看出,对函数使用partial方法之后,生成的newadd为新函数,和add函数不是一个,方法的内存地址已变
- 下方代码显示,给key-word的变量c的值,已经写入函数的默认值中了;使用关键字参数传参,可以改变偏函数中设置函数的参数信息
- 代码示例:
import functools
def add(x:int,y:int,z:int=9,*args,c,**kwargs):
print(x,y,z,c)
print(args)
print(kwargs)
print("*********分割线************")
newadd = functools.partial(add,c=0)
print(newadd(7,8,c=10))
print(newadd(1,2,3,4,5,6,7,8))
import inspect
print(inspect.signature(newadd))
================run_result============
7 8 9 10
()
{}
*********分割线************
None
1 2 3 0
(4, 5, 6, 7, 8)
{}
*********分割线************
None
(x: int, y: int, z: int = 9, *args, c=0, **kwargs)
- 如果使用位置代码传参,偏函数固定的参数建议不要是位置参数,因为会报参数重复,代码如下:
import functools
def add(x:int,y:int,z:int=9,*args,c,**kwargs):
print(x,y,z,c)
print(args)
print(kwargs)
print("*********分割线************")
newadd = functools.partial(add,x=0)
print(newadd(7,8,c=10))
====================run_result=============
Traceback (most recent call last):
File "2022=10/2022-10-14.py", line 100, in <module>
print(newadd(7,8,c=10))
TypeError: add() got multiple values for argument 'x'
- 如果
functools.partial
中设置的默认参数不是指定值,其会按照位置参数的顺序默认赋值,代码如下:
import functools
def add(x:int,y:int,c:int,z:int=9,*args,**kwargs):
print(x,y,c,z)
print(args)
print(kwargs)
print("*********分割线************")
newadd = functools.partial(add,100,200,300)
print(newadd(7,8))
print(newadd(1,2,3,4,5,6,7,8))
import inspect
print(inspect.signature(newadd))
==============run_result============
100 200 300 7
(8,)
{}
*********分割线************
None
100 200 300 1
(2, 3, 4, 5, 6, 7, 8)
{}
*********分割线************
None
(z: int = 9, *args, **kwargs)
lru_cache方法
- 用法:
@functools.lru_cache(maxsize=128, typed=False)
,带参数的装饰器函数 - Least-recently-used装饰器,lru的含义是:最近最少使用,cache缓存
- 如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长,当maxsize是二的幂时,LRU功能执行的最好
- 如果typed设置为True,则不同类型的函数参数将单独缓存,例如:f(3)和f(3.0)则将被视为具有不同结果的不同调用
- 返回一个字典缓存被装饰函数的调用和返回值
import time
import functools
@functools.lru_cache(typed=True)
def add(x,y,z=3):
time.sleep(3)
return x+y+z
print(add(4,5))
print(add(4.0,5))
print(add(y=6,x=4))
print(add(4,6))
============run_result===========
12
12.0
13
13
【代码解析】
- 当typed为False或者不传时,
print(add(4,5))
和print(add(4.0,5))
取的是同一块缓存,从其打印时间差不多可以知道,typed为True时,这两个取的不是同一个缓存 - 默认值传参和位置传参,两个取的缓存也不一致,及时两个传的参数相同
斐波那契数列的改造
- 使用lru_cache装饰器改造斐波那契的递归数列,速度会很快,因为不需要每次计算
- 代码如下:
import functools
@functools.lru_cache()
def fib(n):
if n<2:
return n
return fib(n-1)+fib(n-2)
print([fib(x) for x in range(45)])
============run_result=============
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987……]
lru_cache方法的应用
- 使用前提:
- 同样的函数参数一定得到相同的结果
- 函数执行时间很撑,切要多次执行
- 本质:函数调用的参数——>返回值
- 缺点:
- 不支持缓存火气,key无法过期,失效
- 不支持清除操作
- 不支持分布式,只是一个单机的缓存(单进程,单线程)
- 适用场景:单机上需要空间换时间的地方,可以用缓存来将计算变成快速查询;实现这种查找不会放在列表中,一般放在set或者字典中来实现
缓存的简单介绍
- 缓存:减少后面的压力,大大提高后面的速度,让使用者觉得结果出现的特别快
- lru_cache方法建议使用在key恒定不变的场景下,对于一段时间不变,过一段时间会变化的场景,建议不要使用此方法
- cache(缓存)中有一个很重要的名词,叫做命中。指的是在缓存中找到某个关键词并获取的过程
- 缓存系统在设计之后,key信息不能一直发生变化,否则会产生很多垃圾数据
- 缓存系统在设计之后,无法实现命中功能 ,那缓存系统的存在就没有意义了