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

  • 相关阅读:
    游标cursor
    SQL: EXISTS
    LeetCode Reverse Integer
    LeetCode Same Tree
    LeetCode Maximum Depth of Binary Tree
    LeetCode 3Sum Closest
    LeetCode Linked List Cycle
    LeetCode Best Time to Buy and Sell Stock II
    LeetCode Balanced Binary Tree
    LeetCode Validate Binary Search Tree
  • 原文地址:https://www.cnblogs.com/xixi18/p/11435702.html
Copyright © 2011-2022 走看看