Python类型注解

(一)函数定义的弊端

  • Python是动态语言,变量随时可以被赋值,且能赋值为不同的类型
  • Python不是静态编译型语言,变量是在运行期决定的
  • 动态语言很灵活,但是这种特性也是弊端
def add(x, y):
    return x + y
print(add(4, 5))
print(add('hello','word'))
print(add(4, 'word'))

难发现:由于不做任何类型检查,织到运行期间问题才显现出来,或者线上运行时才能暴漏出问题
难使用:函数的使用者看到函数的时候,并不知道你的函数的设计,并不知道应该传入什么类型的数据
如何解决这种动态语言定义的弊端

  • 增加文档Documentation String
    这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档
    函数定义更新了,文档未必同步更新
def add(x, y):
    """

    :param x:
    :param y: 
    :return:
    """
    return x + y
print(help(add))
  • 函数注解
def add(x:int, y:int) -> int:
    """

    :param x:
    :param y:
    :return:
    """
    return x + y
print(help(add))
print(add(4, 5))
print(add('pyt','hon'))
  • 函数注解:
    python 3.5引入
    对函数的参数进行类型注解
    对函数的返回值进行类型注解
    只队函数的参数做一个辅助的说明,并不对函数的参数进行类型检查
    提供第三方工具,做代码分析,发现隐藏的bug
    函数注解的信息,保存在__annotations__属性中
print(add.__annotations__)
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
  • 变量注解:
    Python 3.6引入,注意它也只是一种对变量的说明,非强制
i:int = 3
  • 业务应用:
    函数参数类型检查
    思路:
    函数参数的检查,一定是在函数外,使用装饰器,要可以复用
    函数应该作为参数,传入到检查函数中
    检查函数拿到函数传入的实际参数,与形参声明对比
    __annotations__属性是一个字典,其中包括返回值类型的声明,假设要做位置参数的判断,无法和字典中的声明对比.使用inspect模块
    inspet模块
    提供获取对象信息的函数,可以检查函数的类,类型检查
    inspect模块
  • signature(callable),获取签名(函数签名包含了一个函数的信息,包含函数名,它的参数类型,它所在的类和名称空间及其他信息)
import inspect

def add(x:int,y:int,*args,**kwargs) -> int:
    print(*args,**kwargs)
    return x + y
sig = inspect.signature(add)
print(sig,type(sig))   # 函数签名
print('parms:',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'>
parms: 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.inbuiltin(print)           # 是否是内建对象

还有很多is函数,需要的时候直接查阅inspect模块帮助

Parameter对象(这玩意绕得很)

保存在元组中,是只读的
name,参数的名字
annotation,参数的注解,可能没有定义
default,参数的缺省值,可能没有定义
empty,特殊的类,用来标记default属性或者注释annotation属性的空值
kind,实参如何绑定到形参,就是形参的类型

POSITIONAL_ONLY         # 值必须是位置参数提供  
    POSITIONAL_OR_KEYWORD   # 值可以作为位置参数或者关键字参数提供  
    VAR_POSITIONAL          # 可变位置参数 *args  
    KEYWORD_ONLY            # keyword-only参数  
    VAR_KEYWORD             # 可变关键字参数
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 = '\n\n')


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,functools



def check(fn):
    @functools.wraps(fn)
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        params = sig.parameters
        print(params)
        values = list(params.values())
        for i,p in enumerate(args):
            param = values[i]
            if params.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].annottation):
                print(k,v,'!===',params[k].annottation)
        return fn(*args,**kwargs)
    return wrapper

@check
def add(x,y:int=7,*args,z,t=10,**kwargs) -> int:
    return x + y