Python的typing模块杂记

typing模块概论

Python是一门动态语言,如果不加上注释的话,很难在阅读阶段去搞清楚函数参数类型或者返回值类型,Python的typing模块可以很好的解决这个问题。

Python 运行时并不强制标注函数和变量类型。类型标注可被用于第三方工具,比如类型检查器、集成开发环境、静态检查器等。去进行语法分析,智能提示等目的使用。但标注上函数和变量类型,有利于开发者阅读代码,所以最好都使用上。

PEP483开始引入了类型提示理论(The Theory of Type Hints)PEP484则正式引入类型提示。

typing模块的主要作用有:

  1. 类型检查,防止运行时出现参数、返回值类型不符。
  2. 作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
  3. 模块加入不会影响程序的运行不会报正式的错误,pycharm支持typing检查错误时会出现黄色警告。

typing模块的一些具体技术

类型别名(type alias)

要定义一个类型别名,可以将一个类型赋给别名。类型别名可用于简化复杂类型签名,在下面示例中,Vectorlist[float] 将被视为可互换的同义词:如下面的代码所示:

 1# type_alias_sample.py
 2
 3Vector = list[float] # 这表明,Vector是一个列表,且里面的元素都是float类型
 4
 5# scalar:float 表示参数是参数应是float类型
 6# vector:Vector 表示参数是参数应是列表类型,且里面的元素float都是float
 7def scale(scalar:float,vector:Vector) -> Vector:
 8	return [scalar * num for num in vector] # 将列表vector中的每个元素取出,和scalar相乘,然后再存进一个列表中,最后返回
 9
10v = [1.0, -4.2, 5.4]
11print(v)
12new_vector = scale(2.0, v)  # 类型检查器会敲定这个浮点数列表就是Vector类型
13print(new_vector)
14	

NewType辅助类

使用 NewType辅助类,创建不同的新类型,静态类型检查器会将新类型视为它是原始类型的子类。如下面的代码所示:

 1# new_type_sample.py
 2
 3from typing import NewType  # 显式地从typing模块中导入NewType辅助类
 4
 5# 定义一个新类型UserId,且该类型是继承自int类型
 6UserId = NewType('UserId', int) 
 7
 8 # 声明函数的参数类型是UserId,返回值类型是字符串
 9def get_user_name(user_id: UserId) -> str:
10	return str(user_id)
11
12#  执行类型检查,显式地声明整数42351是一个UserId
13user_a = get_user_name(UserId(42351)) 
14print("user_a id is "+user_a)
15
16#  执行类型检查,整数-1不是一个UserId,但执行get_user_name函数也并不会出错
17user_b = get_user_name(-1)
18print("user_b id is "+user_b)

在运行时,NewType语句将生成一个可调用对象,它会立即返回传递给它的任何参数。 这意味着NewType语句并不会创建新类,也不会引入比常规函数调用更多的开销。如果使用class语句去继承这个新定义的UserId,例如下面的代码:

1class AdminUserId(UserId): 
2	pass

在运行时会报错。但如果继续使用一个NewType语句去“继承自”UserId

1from typing import NewType
2UserId = NewType('UserId', int)
3ProUserId = NewType('ProUserId', UserId)

则是可以正常运行的。

TypeVar辅助类

使用TypeVar辅助类,可以定义一个“泛型类型”,这个泛型类型既可以指代一切类型,也可以特指某几种类型。例如下面的代码所示:

 1#type_var_sample.py
 2
 3from typing import TypeVar  # 显式地从typing模块中导入TypeVar辅助类
 4
 5T = TypeVar('T')            # 定义一个“泛型类型”T,这个类型可以指代一切
 6A = TypeVar('A',str,tuple)  # 定义一个“泛型类型”A,这个类型可以只可以指代str类型和tuple类型
 7
 8# 返回一个带n个重复元素x的列表,列表中元素的类型是可以任意类型
 9def repeat(x: T, n: int) -> list[T]:
10    return [x]*n
11
12list_1 = repeat("abc",5)
13print("list_1 is ",list_1)   # 打印结果 list_1 is ['abc','abc','abc','abc','abc']
14list_2 = repeat((1,2,3),4)   # 打印结果 list_2 is [(1,2,3),(1,2,3),(1,2,3),(1,2,3)]
15print("list_2 is ",list_2)
16list_3 = repeat({1:"one",2:2,"three":3},3)
17print("list_3 is ",list_3)   # 打印结果 list_3 is [{1:'one',2:2,'three',3},{1:'one',2:2,'three',3},{1:'one',2:2,'three',3} ]
18
19# 比较两者中元素个数,返回元素个数较多的那个输入参数
20def longest(x:A,y:A) -> A:
21    if len(x) >= len(y):
22        return x
23    else:
24        return y
25
26print(longest("abc",('a','b')))         # 打印结果abc
27print(longest("abc",('a','b','c','d'))) # 打印结果('a','b','c','d')

在运行时,isinstance(x, T) 将引发 TypeError 。通常,isinstance()issubclass() 不应与类型一起使用。通过查看PEP484可以获取更多细节。

参考网页

Python中typing模块详解 Python typing.TypeVar实例讲解