zoukankan      html  css  js  c++  java
  • 8.1python类型注解

    一.函数定义的弊端
    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='
    
    ')
    #返回内容:
    (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='
    
    ')
    
            #位置参数处理
            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,'			',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>适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询

  • 相关阅读:
    codeforces 713A A. Sonya and Queries(状态压缩)
    2016大连网赛
    hdu-5834 Magic boy Bi Luo with his excited tree(树形dp)
    codeforces gym-101078
    ifrog-1028 Bob and Alice are playing numbers(trie树)
    codeforces 477B B. Dreamoon and Sets(构造)
    codeforces 477A A. Dreamoon and Sums(数学)
    三角形划分区域
    当总统
    Friends number
  • 原文地址:https://www.cnblogs.com/xixi18/p/11435702.html
Copyright © 2011-2022 走看看