一、背景
Python是众所周知的动态类型语言, 其动态性主要体现的一方面是类型动态,通常定义变量或函数时无需显示指明其类型,由解释器在解释时去判断变量或函数返回的类型。Python基于鸭子协议和天生泛型的特点使得开发的效率变高了,但同样也带来一些问题,由于自由度变高,不强制使用类型声明,很多开发者都会按照自己编码的特性去编码,导致代码不规范。特别是大型项目中,团队内开发,对于复杂的逻辑,如果不显示指明变量或函数返回的类型,一方面代码Review时变得更加困难,另一方面增加了潜在的BUG。因此Python3.5 Version下推出了types模块,用于实现类型检查和类型自定义等规范化功能,提高安全型。
既然Python自身由于鸭子协议,没做完整的类型检查,那么就由types来实现变量,形参,函数的类型检查。
二、常用类型提示
- int,float,bool,str:整形,实型,布尔,字符串
- List,Dict,Tuple,Set: 列表,字典,元组,集合
- Iterable, Iterator,Generator:可迭代对象,迭代器,生成器
- None: 空类型,全局实例唯一
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class TypesUsing(object):
def test1(self, name: str, age: int, hobby: List[str], family: Dict[str, int], friends: Tuple[str, str]): pass
def test2(self, is_master: bool, salary: float): pass
def test3(self, hobby: Iterable, dreams: Iterator, plan: Generator): pass
async def run(): await asyncio.sleep(1)
def run2(): yield from [1, 2, 3, 3, 4, 5]
t = TypesUsing() t.test1('syz', 24, ['swim', 'run'], {'father': 1, 'mother': 1}, ('zjw', 'wm')) t.test2(True, 15000.0) t.test3(['swim', 'run'], iter(['1', '2', '3']), run2())
|
三、Callable类型提示
Callable类型表示修饰的对象是一个可调用的对象。
types模块中Callable用法:
obj: Callable[[int, int], str]
表示obj对象是一个包含两个int类型的参数,返回值为str类型的可回调对象。
举一个有点强度的举例:
先给个没有任何类型提示的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class APIRouter(object):
def add_route(self, path, methods): def decorator(func): print('do other things') return func
return decorator
def get(self, path): return self.add_route(path, ['GET'])
class Application(object):
def __init__(self): self.router = APIRouter()
def get(self, path): return self.router.get(path) @app.get('/api/user-info') def get_info(): print('获取个人信息')
|
上述这段代码对于使用装饰器不够熟练的开发者来说,阅读起来比较吃力,需要去仔细分析每个参数以及每个函数的返回值是什么,如果这是业务代码,对于要快速理解这段代码的意思,需要花些时间。所以抛弃掉上述代码,来看下面这段加了类型提示的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class APIRouter(object):
def add_route(self, path: str, methods: Optional[List[str]] = None) -> Callable[[Callable], Callable]: def decorator(func: Callable) -> Callable: print('do other things') return func
return decorator
def get(self, path: str) -> Callable[[Callable], Callable]: return self.add_route(path, ['GET'])
class Application(object):
def __init__(self): self.router = APIRouter()
def get(self, path: str) -> Callable[[Callable], Callable]: return self.router.get(path)
app = Application()
@app.get('/api/user-info') def get_info(): print('获取个人信息')
|
在我们阅读很多框架源码时,往往会发现框架中的源码并不会写太多的注释,有时候甚至往往不写注释,因此快速理解某一段代码的意思,关键点有两个,一个是函数/变量名,另一个时函数返回类型/变量类型,通过这两个关键点可以快速理解该功能轮廓,然后再进一步进入函数体内部深入细节。
当遇见Callable[[Callable], Callable]
,函数的参数为某一个函数,返回这也为函数,这不就是典型的Python中的装饰器嘛。接着再来分析这段代码, 上述代码其实是装饰器工厂函数的变形体, 主要功能是在执行get_info前 do others things.
@app.get('/api/user-info')
返回的类型为Callable[[Callable], Callable]
,表明它是一个装饰器decorator,它修饰的函数get_info
作为装饰器的参数func
。
这里稍微提一下一个知识点,装饰器的导入时和运行时,do other things会在真正调用get_info
方法前执行。比如添加路由,然后再路由映射执行get_info
方法。
四、Optional类型提示
在实际开发中,传入函数的参数是可变的,即并不是所有的参数都需要传入,对于List,Dict,Set等可变对象来说,不能将他们设置成默认值,会导致数据出错,需要将其设置成None(可变防御参数),所以为了兼容None类型,使用Optional来包裹其他类型。
types模块中Optional用法: hobby: Optional[List[int]]
举例:
1 2
| def func(name: str, hobby: Optional[List[str]]) -> None: pass
|
五、任意类型TypeVar
当不确定某个变量是什么类型时,可以使用TypeVar('T')
修饰变量类型, T为类型名,如果限定变量为str或bytes,可以使用AnyStr = TypeVar('AnyStr', bytes, str)
。
1 2 3
| class TypeVar(_TypingBase, _root=True): def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False):
|
举例:
1 2 3 4 5 6 7 8
| T = TypeVar('T') AnyStr = TypeVar('AnyStr', bytes, str) def func3(name: T) -> None: print(name)
func3('SSS') func3(111)
|
假设代码如下:
1 2 3 4 5 6 7
| T = TypeVar('T', 'str') def func3(name: T) -> None: print(name)
func3('SSS') func3(111)
|
六、Union类型
上述例子中提到了AnyStr = TypeVar('AnyStr', bytes, str) # 类型名为AnyStr,只允许为bytes和str类型的变量
,通过Union
类型也可以等价的实现,name: Union[bytes, str]
。
Union[X, Y] means either X or Y.
举例:
1 2
| def func3(hobby: Union[str, List[int]]) -> None: print(name)
|
未完待续~