目录javascript
类型注解,即对变量的类型,进行标注或者说明,由于Python是一门动态编译型语言,咱们没法在赋值时就定义它的变量类型,因此在Python3.5
以上版本新增了类型注解,但仅仅是提示做用,并不能严格控制,这是动态编译型语言的通病,下面来仔细看一下什么是Python的类型注解。java
Python是动态语言,变量随时能够被赋值,且赋值为不一样的类型,这就与静态语言不一样了,变量的类型是在运行期决定的,而静态语言事先就已经定义好了变量的类型了。这是动态语言方便之处,但也是一种弊端,咱们没法控制变量的类型,也就没法控制异常的产生。举个栗子python
def add(x,y): return x + y print(add(1,2)) print(add('s','b')) print(add(1,'a'))
当用户传入两个数字时,返回它们的和,可是若是咱们传递其余变量呢?好比字符串,由于Python中实现了+号的类型重载,因此说两个字符串的确能够加,可是若是是数字和字符串呢?在Python这种强类型语言中来讲,属于非法操做(javascript会隐式转换),而这时,咱们就须要对用户传入的数据进行类型判断,不符合本函数的需求,那么就抛个异常,或者提示等等操做,这样就不会引发后续代码在执行期崩溃。如何解决呢?其实主要有两种方式。app
在函数中插入说明性文档的方式成为函数文档。框架
def add(x, y): """ This function used to add something :param x: int object :param y: int object :return: int object """ return x + y
在函数中,通常是定义语句后的首行使用三对双引号表示。一般存储在函数的__doc__属性中。当用户使用help(函数)时,会被打印在屏幕上。函数
In [68]: def add(x, y): ...: """ ...: This function used to add something ...: :param x: int object ...: :param y: int object ...: :return: int object ...: """ ...: return x + y ...: In [69]: help(add) Help on function add in module __main__: add(x, y) This function used to add something :param x: int object :param y: int object :return: int object In [70]: print(add.__doc__) This function used to add something :param x: int object :param y: int object :return: int object In [71]:
每次都要使用help来查看函数的说明,的确可让使用者了解函数的参数以及返回值的类型,但并非全部人都愿意写doc的,在这个所谓的敏捷开发时代,人们大多会以敏捷开发为借口没时间写,因此这种方法不是很用。工具
Python的函数注解是什么呢?首先来看一下以下代码:code
def add(x: int, y: int) -> int: return x + y
完成以上定义后,,主要的差异以下图:
协程
当咱们在IDE中准备传入非注释类型变量时,IDE会帮咱们进行颜色提示,用于表示这里传入的变量有点问题。在编写时咱们尚且可使用这种方式,对咱们产生一点'警示',可是当咱们写的函数被其余人调用的时候,那么就没法进行'提示'了,这个时候,咱们就须要对传入的参数进行类型检查了。对象
咱们来总结一下:
函数注解的信息,保存在函数的__annotation__
属性中。
python3.6
以上还添加了变量的注解:i:int = 10
,固然也只是提示的做用。
在Python中使用__开头的表示符通常被用特殊属性,__annotation__
存储的就是函数的签名信息
In [71]: def add(x: int, y: int) -> int: ...: return x + y ...: In [73]: add.__annotations__ Out[73]: {'x': int, 'y': int, 'return': int}
当咱们使用变量注释时,变量名和类型就会存放在函数的__annotations__属性中。那么即然有变量存储,那么咱们是否是只须要获取传入的参数,而后和annotations中存储的变量类型进行比较是否是就达到目的了呢?仔细思考一下:
下面咱们来了解一下inspect模块,它能够帮咱们完成这个事情。
官方解释以下:inspect模块提供了几个有用的函数来帮助获取关于活动对象的信息,例如模块、类、方法、函数、回溯、框架对象和代码对象。例如,它能够帮助您检查类的内容、检索方法的源代码、提取并格式化函数的参数列表,或者获取显示详细回溯所需的全部信息。
分类 | 方法名称 | 功能 |
---|---|---|
判断 | inspect.getmodulename(path) | 获取模块名称 |
inspect.ismodule(object) | 是否是个模块 | |
inspect.isclass(object) | 是否是个类 | |
inspect.ismethod(object) | 是否是一个方法 | |
inspect.isfunction(object) | 是否是一个函数 | |
inspect.isgeneratorfunction(object) | 是否是一个生成器函数 | |
inspect.isgenerator(object) | 是否是一个生成器 | |
inspect.iscoroutinefunction(object) | 是否是一个协程函数 | |
获取信息 | inspect.getmodulename(path) | 获取模块名称 |
inspect.getsource(object) | 获取对象的原码(并不会解析装饰器原码) |
首先咱们要说的是函数的签名信息:它包含了了函数的函数名、它的参数类型,它所在的类和名称空间及其余信息,签名对象(signature object)表示可调用对象的调用签名信息和它的注解信息,当咱们使用signature()时,它会从新返回一个包含可调用对象信息的签名对象。
signature类的parameters
属性,它里面存放的是函数的参数注解和返回值注解,组成的有序字典,其中参数注解的格式为:参数名称,使用inspect.Parameters类包装的参数注解,这个参数注解很强大,它包含以下经常使用的方法:
方法名称 | 含义 |
---|---|
empty | 等同于inspect._empty表示一个参数没有被类型注释 |
name | 参数的名称 |
default | 参数的默认值,若是一个参数没有默认值,这个属性的值为inspect.empty |
annotation | 参数的注解类型,若是参数没有定义注解,这个属性的值为inspect.empty |
kind | 参数的类型 |
这里的参数类型表示的是inspect内置参数类型(其实就是几个经常使用的函数参数定义类型而已,只是换个名字而已)
_POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY # 位置参数_only _POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD # 位置或关键字参数 _VAR_POSITIONAL = _ParameterKind.VAR_POSITIONAL # 可变位置参数 _KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY # keyword-only参数 _VAR_KEYWORD = _ParameterKind.VAR_KEYWORD # 可变关键字参数
其中POSITIONAL_ONLY,Python中没有被实现。
根据上面讲的方法,咱们能够经过以下方式,简单的获取参数的签名:
In [11]: import inspect ...: ...: def add(x: int, y: int) -> int: ...: return x + y ...: ...: sig = inspect.signature(add) ...: params = sig.parameters ...: print(params) OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)]) In [21]: params['x'].annotation Out[21]: int # 若是没有定义x的参数注解,那么这里就是inspect._empty
经过它的属性,搭配有序字典
这个特性,有没有很兴奋?参数有序,传入的实参有序,还能获取参数注解的类型,那么就能够开工进行参数检查了!
以上面函数为例子,当给add函数传入的x,y时进行参数检查,若是x,y不是int类型,那么返回异常,并退出函数
import inspect import functools def check(fn): @functools.wraps(fn) # 等于 wrapper.__annotation__ = fn.__annotation__ 还有其余的属性好比__doc__,__module__等 def wrapper(*args, **kwargs): sig = inspect.signature(fn) # 获取add函数签名信息 params = sig.parameters # 获取add函数的参数信息 values = list(params.values()) # 因为params是个有序字典,那么values也是有序的,只需根据索一一对应判断便可 for i, k in enumerate(args): # 遍历用户传入的位置参数 if values[i].annotation != inspect._empty: # 若是定义了参数注解,则开始检查 if not isinstance(k, values[i].annotation): # 若是检查不经过,曝出异常 raise('Key Error') for k,v in kwargs.items(): if params[k].annotation != inspect._empty: if not isinstance(v,params[k].annotation): raise('Key Error') return fn(*args, **kwargs) return wrapper @check def add(x: int, y: int) -> int: return x + y add(4,y=5)