• Python类型注解(inspect模块)


    函数定义的弊端

    Python是动态语言,变量随时可以被赋值,且能赋值为不同的类型,同时Python不是静态编译型语言,变量类型是在运行器决定的,动态语言很灵活,但是这种特性也是弊端。

    def add(x, y):
        return x + y
    
    print(add(4, 5))
    print(add('hello', 'world'))
    add(4, 'hello') #
    
    结果为:
    9
    helloworld
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-124-8b7c71b24ec5> in <module>
          4 print(add(4, 5))
          5 print(add('hello', 'world'))
    ----> 6 add(4, 'hello') #
    
    <ipython-input-124-8b7c71b24ec5> in add(x, y)
          1 def add(x, y):
    ----> 2     return x + y
          3 
          4 print(add(4, 5))
          5 print(add('hello', 'world'))
    
    TypeError: unsupported operand type(s) for +: 'int' and 'str'

    上面的第三个add报错,很多时候,这样的错误很难发现,由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行时才能暴露出问题,而且函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类型的数据。那应该如何解决这种动态语言定义的弊端呢?

    这个时候就需要增加一个文档,documentation string ,当然,这只是一个惯例,并不是强制标准,不能要求程序员一定要为函数提供说明文档。但有时候,函数定义更新了,文档未必同步更新。

    def add(x, y):
        '''
        :param x: int
        :param y: int
        :return: int
        '''
        return x + y
    
    print(help(add))
    
    结果为:
    
    Help on function add in module __main__:
    
    add(x, y)
        :param x: int
        :param y: int
        :return: int
    
    None

    函数注解(function annotation)

    函数注解可以解决这种动态语言定义的弊端。

    def add(x:int , y:int) -> int :
        '''
        :param x: int
        :param y: int
        :return: int
        '''
        return x + y
    
    print(help(add))
    print(add(4, 5))
    print(add('mag', 'edu'))
    
    结果为:
    
    Help on function add in module __main__:
    
    add(x: int, y: int) -> int
        :param x: int
        :param y: int
        :return: int
    
    None
    9
    magedu

    函数注解是Python3.5以后引入的,它是对函数的参数进行类型注解,同时对函数的返回值进行类型注解,它只对函数参数做一个辅助的说明,并不对函数参数进行类型检查。提供给第三方工具,做代码分析,发现隐藏的bug,含住注解的信息,保存在__annotations__属性中。

    def add(x:int , y:int) -> int :
        '''
        :param x: int
        :param y: int
        :return: int
        '''
        return x + y
    
    add.__annotations__
    
    结果为:
    {'x': int, 'y': int, 'return': int}

    同时在Python3.6以后引入了变量注解,i : int = 3。

    i:int =5
    print(i)
    
    结果为:
    5

    业务应用

    函数参数类型检查,它的思路为:函数参数的检查,一定是在函数外,函数应该作为参数,传入到检查函数中,检查函数拿到函数传入的实际参数,与形参声明对比,_annotations__属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用inspect模块。

    inspet模块提供获取对象信息的函数,可以检查函数和类、类型检查。

    inspect模块

    signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)

    import inspect
    def add(x:int, y:int, *args,**kwargs) -> int:
        return x + y
    sig = inspect.signature(add)
    print(sig, type(sig)) # 函数签名
    print('params : ', sig.parameters) # OrderedDict有序字典
    print('return : ', sig.return_annotation)
    print(sig.parameters['y'], type(sig.parameters['y']))
    print(sig.parameters['x'].annotation)
    print(sig.parameters['args'])
    print(sig.parameters['args'].annotation)
    print(sig.parameters['kwargs'])
    print(sig.parameters['kwargs'].annotation)
    
    结果为:
    
    (x: int, y: int, *args, **kwargs) -> int <class 'inspect.Signature'>#函数签名
    params :  OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
    return :  <class 'int'>
    y: int <class 'inspect.Parameter'>
    <class 'int'>
    *args
    <class 'inspect._empty'>
    **kwargs
    <class 'inspect._empty'>

    inspect.isfunction(add),是否是函数
    inspect.ismethod(add)),是否是类的方法
    inspect.isgenerator(add)),是否是生成器对象
    inspect.isgeneratorfunction(add)),是否是生成器函数
    inspect.isclass(add)),是否是类
    inspect.ismodule(inspect)),是否是模块
    inspect.isbuiltin(print)),是否是内建对象
    还有很多is函数,需要的时候查阅inspect模块帮助

    Parameter对象

    保存在元组中,是只读的。name,参数的名字,annotation,参数的注解,可能没有定义,default,参数的缺省值,可能没有定义,empty,特殊的类,用来标记default属性或者注释annotation属性的空值。kind,实参如何绑定到形参,就是形参的类型。POSITIONAL_ONLY,值必须是位置参数提供(没有实现),POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供,VAR_POSITIONAL,可变位置参数,对应*args,KEYWORD_ONLY,keyword-only参数,对应*或者*args之后的出现的非可变关键字参数,VAR_KEYWORD,可变关键字参数,对应**kwargs。

    import inspect
    def add(x, y:int=7, *args, z, t=10,**kwargs) -> int:
        return x + y
    
    sig = inspect.signature(add)
    print(sig)
    print('params : ', sig.parameters) # 有序字典
    print('return : ', sig.return_annotation)
    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

    业务应用

    现在有函数如下, 请检查用户输入的是否符合参数注解的要求。

    def add(x, y:int=7) -> int:
        return x + y

    解决这个问题,思路可以是这样的,调用时,判断用户输入的实参是否符合要求,调用时,用户感觉上还是在调用add函数,然后对用户输入的数据和声明的类型进行对比,如果不符合,提示用户。

    装饰器解决

    import inspect
    
    def check(fn):
        def wrapper(*args,**kwargs):
            #实参检查
            #print(*args,**kwargs)#这句会出错,比如,4,y = 7,解构后相当于y = 7是print函数的关键字参数,但是没有这个关键字参数。
            print(*args,kwargs)
            
            sig = inspect.signature(add)#这写错了,add应该是fn
            print(sig)
            print('params : ', sig.parameters)
            print('return : ', sig.return_annotation)
            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='
    
    ')
           
            ret = fn(*args,**kwargs)
            return ret
        return wrapper
    
    @check
    def add(x:int,y:int=7) ->int:
        return x+y
    
    add(4,8)

    结果为:
    4 8 {}
    (*args, **kwargs)
    params :  OrderedDict([('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
    return :  <class 'inspect._empty'>
    ~~~~~~~~~~~~~~~~
    1 args <class 'inspect._empty'> VAR_POSITIONAL <class 'inspect._empty'>
    True
    
    2 kwargs <class 'inspect._empty'> VAR_KEYWORD <class 'inspect._empty'>
    True
    
    
    Out[6]:
    12

    怎么判断?

    import inspect
    
    def check(fn):
        def wrapper(*args,**kwargs):
            #实参检查
            #print(*args,**kwargs)#这句会出错,比如,4,y = 7,解构后相当于y = 7是print函数的关键字参数,但是没有这个关键字参数。
            print(*args,kwargs)
            
            sig = inspect.signature(fn)
            print(sig)
            print('params : ', sig.parameters)
            print('return : ', sig.return_annotation)
            print('~~~~~~~~~~~~~~~~')
            params = sig.parameters#有序字典
            
           # for param in sig.parameters.values():
               # print(param.name,param)
               # print(param.name, param.annotation, param.kind, param.default) 只是为了参照
           
        
            
            #关键字传参处理
            for k,v in kwargs.items():
                if isinstance(v,params[k].annotation):
                    print("==")
                    
            #位置参数处理
            param_list=list(params.keys())
            for i,x in enumerate(args):
                k = param_list[i]
                if isinstance(v,params[k].annotation):
                    print("==")    
                
            
            ret = fn(*args,**kwargs)
            return ret
        return wrapper
    
    @check
    def add(x:int,y:int=7) ->int:
        return x+y
    
    add(4,y=8)
    import inspect
    def add(x, y:int=7) -> int:
        return x + y
    
    def check(fn):
        def wrapper(*args, **kwargs):
            sig = inspect.signature(fn)
            params = sig.parameters
            values = list(params.values())
            for i,p in enumerate(args):
                if isinstance(p, values[i].annotation): # 实参和形参声明一致
                    print('==')
            for k,v in kwargs.items():
                if isinstance(v, params[k].annotation): # 实参和形参声明一致
                    print('===')
            return fn(*args, **kwargs)
        return wrapper
    
    check(add)(20,10)
    
    结果为:
    ==
    30
    
    check(add)(20,y=10)
    结果为:
    ===
    30
    
    check(add)(y=10,x=20)
    结果为:
    ===
    30

    业务需求是参数有注解就要求实参类型和声明应该一致,没有注解的参数不比较,如何修改代码?

    import inspect
    
    def check(fn):
        def wrapper(*args, **kwargs):
            sig = inspect.signature(fn)
            params = sig.parameters
            values = list(params.values())
            for i,p in enumerate(args):
                param = values[i]
                if param.annotation is not param.empty and not isinstance(p, param.annotation):
                    print(p,'!==',values[i].annotation)
            for k,v in kwargs.items():
                if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
                    print(k,v,'!===',params[k].annotation)

            else:
    errstr = "{} {} {}".format(v,"is not",params[k].annotation)
    print(errstr)
    raise TypeError(errstr)

    return fn(*args, **kwargs)
        return wrapper
    @check
    def add(x, y:int=7) -> int:
        return x + y
    
    add(20,10)
    
    结果为:
    30
    
    add(20,y=10)
    结果为:
    30
    
    add(y=10,x=20)
    结果为:
    30
     
     
     
  • 相关阅读:
    在jQuery中Ajax的Post提交中文乱码的解决方案
    mysql 日期时间型的按日期分组
    mysql 逗号分隔的id转为逗号分隔的名称
    阿米在日本工作生活趣事(2)
    阿米在日本工作生活趣事(1)
    com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
    File exists.If no other git process is currently running,
    带小数点的String 转int java.lang.Double cannot be cast to java.lang.Integer
    Jboss解决只能通过localhost访问而不能使用IP访问项目的问题
    This method accesses the value of a Map entry, using a key that was retrieved from a keySet iterator. It is more efficient to use an iterator on the entrySet of the map, to avoid the Map.get(key) look
  • 原文地址:https://www.cnblogs.com/xpc51/p/11710646.html
Copyright © 2020-2023  润新知