8.1python类型注解

一个人想着一个人 提交于 2019-11-29 00:31:31

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!