详解Python types类型模块

一、背景

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):
# do other things
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:
# do other things
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) # 类型名为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) # 报错, TypeError: A single constraint is not allowed

六、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)

未完待续~