前置学习 为什么会有类型提示
Python 是一种动态类型语言,这意味着我们在编写代码的时候更为自由,运行时不需要指定变量类型
但是与此同时 IDE 无法像静态类型语言那样分析代码,及时给我们相应的提示,比如字符串的 split 方法
1 2 def split_str (s ): strs = s.split("," )
由于不知道参数 s 是什么类型,所以当你敲 s. 的时候不会出现 split 的语法提示
解决上述问题,类型提示 Python 3.5、3.6 新增了两个特性 PEP 484 和 PEP 526
帮助 IDE 为我们提供更智能的提示
这些新特性不会影响语言本身,只是增加一点提示
类型提示分类 主要分两个
变量提示:PEP 526 特性加的
函数参数提示:PEP 484 特性加的
变量类型提示 没有使用类型提示 想说明变量的数据类型只能通过注释
1 2 3 4 5 6 7 8 9 10 primes = [] captain = ... class Starship : stats = {}
使用了类型提示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from typing import List , ClassVar, Dict num: int = 0 bool_var: bool = True dict_var: Dict = {} primes: List [int ] = [] class Starship : stats: ClassVar[Dict [str , int ]] = {} num: int
这里会用到 typing 模块,后面会再展开详解
假设变量标注了类型,传错了会报错吗? 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 from typing import List num: int = 0 bool_var: bool = True dict_var: Dict = {} primes: List [int ] = [] num = "123" bool_var = 123 dict_var = [] primes = ["1" , "2" ] print (num, bool_var, dict_var, primes)123 123 [] ['1' , '2' ]
它并不会报错,但是会有 warning,是 IDE 的智能语法提示
所以,这个类型提示更像是一个规范约束,并不是一个语法限制
变量类型提示-元组打包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 a = 1 , 2 , 3 t: Tuple [int , ...] = (1 , 2 , 3 ) print (t)t = 1 , 2 , 3 print (t)t: Tuple [int , ...] = 1 , 2 , 3 print (t)t = 1 , 2 , 3 print (t)(1 , 2 , 3 ) (1 , 2 , 3 ) (1 , 2 , 3 ) (1 , 2 , 3 )
为什么要加 …
不加的话,元组打包的时候,会有一个 warning 提示
变量类型提示-元组解包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 message = (1 , 2 , 3 ) a, b, c = message print (a, b, c) header: str kind: int body: Optional [List [str ]] header, kind, body = ("str" , 123 , ["1" , "2" , "3" ]) header, kind, body = (123 , 123 , ["1" , "2" , "3" ])
Optional 会在后面讲 typing 的时候详解
在类里面使用 1 2 3 4 class BasicStarship : captain: str = 'Picard' damage: int stats: ClassVar[Dict [str , int ]] = {}
ClassVar
是 typing 模块的一个特殊类
它向静态类型检查器指示不应在类实例上设置此变量
函数参数类型提示 不仅提供了函数参数列表的类型提示,也提供了函数返回的类型提示
栗子一 1 2 3 def greeting (name: str ) -> str : return 'Hello ' + name
栗子二 1 2 def greeting (name: str , obj: Dict [str , List [int ]] ) -> None : print (name, obj)
常用类型提示
int,long,float: 整型,长整形,浮点型;
bool,str: 布尔型,字符串类型;
List, Tuple, Dict, Set:列表,元组,字典, 集合;
Iterable,Iterator:可迭代类型,迭代器类型;
Generator:生成器类型;
前两行小写的不需要 import,后面三行都需要通过 typing 模块 import 哦
常用类型提示栗子 指定函数参数类型 单个参数 1 2 3 def greeting (name: str ) : return "hello"
多个参数 1 2 3 def add (a: int , string: str , f: float , b: bool or str ): print (a, string, f, b)
bool or str:代表参数 b 可以是布尔类型,也可以是字符串
指定函数返回的参数类型 简单栗子 1 2 3 def greeting (name: str ) -> str : return "hello"
复杂一点的栗子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from typing import Tuple , List , Dict def add (a: int , string: str , f: float , b: bool or str ) -> Tuple [List , Tuple , Dict , str or bool ]: list1 = list (range (a)) tup = (string, string, string) d = {"a" : f} bl = b return list1, tup, d, bl print (add(1 , "2" , 123 , True ))([0 ], ('2' , '2' , '2' ), {'a' : 123 }, True )
指定类型的时候用 list、set、dict、tuple 可不可以呢? 可以是可以,但是不能指定里面元素数据类型
1 2 def test (a: list , b: dict , c: set , d: tuple ): print (a, b, c, d)
List[T]、Set[T] 只能传一个类型,传多个会报错
1 2 a: List [int , str ] = [1 , "2" ] b: Set [int , str ] = {1 , 2 , 3 }
IDE 不会报错,但运行时会报错
1 2 3 4 5 6 7 8 9 10 Traceback (most recent call last): File "/Users/polo/Documents/test.py" , line 36 , in <module> a: List [int , str ] = [1 , "2" ] File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/typing.py" , line 261 , in inner return func(*args, **kwds) File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/typing.py" , line 683 , in __getitem__ _check_generic(self, params) File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/typing.py" , line 215 , in _check_generic raise TypeError(f"Too {'many' if alen > elen else 'few' } parameters for {cls} ;" TypeError: Too many parameters for typing.List ; actual 2 , expected 1
大致意思就是:List 传了太多参数,期望 1 个,实际 2 个
那 Tuple[T] 传多个会报错吗? 1 2 3 4 5 6 d: Tuple [int , str ] = (1 , "2" ) print (d)(1 , '2' )
是不会报错的
再来看看 Tuple[T] 的多种写法 只写一个 int,赋值两个 int 元素会报 warning 如果 Tuple[T] 指定类型数量和赋值的元素数量不一致呢?
1 d: Tuple [int , str ] = (1 , "2" , "2" )
不会报错,但是也会有 warning
综上两个栗子,得出结论 Tuple[T] 指定一个类型的时候,仅针对同一个索引下的元素类型
如果想像 List[T] 一样,指定一个类型,可以对所有元素生效呢 1 2 d: Tuple [int , ...] = (1 , 2 , 3 ) d: Tuple [Dict [str , str ], ...] = ({"name" : "poloyy" }, {"age" : "33" })
指定一个类型后,在后面加个 … 就行
类型别名 可以将复杂一点类型给个别名,这样好用一些
变量栗子 1 2 3 4 5 6 vector = List [float ] var: vector = [1.1 , 2.2 ] var: List [float ] = [1.1 , 2.2 ]
函数栗子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 vector_list_es = List [float ] vector_dict = Dict [str , vector_list_es] vector_list = List [vector_dict] vector = List [Dict [str , List [float ]]] def scale (scalar: float , vector: vector_list ) -> vector_list: for item in vector: for key, value in item.items(): item[key] = [scalar * num for num in value] print (vector) return vector scale(2.2 , [{"a" : [1 , 2 , 3 ]}, {"b" : [4 , 5 , 6 ]}]) [{'a' : [2.2 , 4.4 , 6.6000000000000005 ]}, {'b' : [8.8 , 11.0 , 13.200000000000001 ]}]
更接近实际应用的栗子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ConnectionOptions = Dict [str , str ] Address = Tuple [str , int ] Server = Tuple [Address, ConnectionOptions] def broadcast_message (message: str , servers: Server ) -> None : print (message, servers) message = "发送服务器消息" servers = (("127.0.0.1" , 127 ), {"name" : "服务器1" }) broadcast_message(message, servers) 发送服务器消息 (('127.0.0.1' , 127 ), {'name' : '服务器1' })
NewType 可以自定义创一个新类型
主要用于类型检查
NewType(name, tp) 返回一个函数,这个函数返回其原本的值
静态类型检查器会将新类型看作是原始类型的一个子类
tp 就是原始类型
实际栗子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from typing import NewTypeUserId = NewType('UserId' , int ) def name_by_id (user_id: UserId ) -> str : print (user_id) UserId('user' ) num = UserId(5 ) name_by_id(42 ) name_by_id(UserId(42 )) print (type (UserId(5 )))42 42 <class 'int' >
可以看到 UserId 其实也是 int 类型
类型检查 1 2 3 4 5 6 7 8 9 10 11 UserId = NewType('UserIds' , int ) def get_user_name (user_id: UserId ) -> str : ... user_a = get_user_name(UserId(42351 )) user_b = get_user_name(-1 )
使用 UserId 类型做算术运算,得到的是 int 类型数据 1 2 3 4 5 6 7 8 9 output = UserId(23413 ) + UserId(54341 ) print (output)print (type (output))77754 <class 'int' >
Callable 是一个可调用对象类型
查看对象是否可调用 语法 1 2 isinstance (对象, Callable )
栗子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def print_name (name: str ): print (name) print (isinstance (print_name, Callable ))x = 1 print (isinstance (x, Callable ))True False
函数是可调用的,所以是 True,而变量不是可调用对象,所以是 False
Callable 作为函数参数 看看 Callable 的源码 1 Callable type ; Callable [[int ], str ] is a function of (int ) -> str .
第一个类型(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 def print_name (name: str ): print (name) def get_name (get_func: Callable [[str ], None ] ): return get_func vars = get_name(print_name)vars ("test" )def get_name_test (func ): return func vars2 = get_name_test(print_name) vars2("森七" ) test 森七
Callable 作为函数返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def get_name_return () -> Callable [[str ], None ]: return print_name vars = get_name_return()vars ("test" )def get_name_test (): return print_name vars2 = get_name_test() vars2("森七" ) test 森七
TypeVar 泛型 源码解析使用方式 1 2 3 4 5 6 7 class TypeVar (_TypingBase, _root=True ): """Type variable. Usage:: T = TypeVar('T') # Can be anything A = TypeVar('A', str, bytes) # Must be str or bytes
任意类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 T = TypeVar('T' ) def test (name: T ) -> T: print (name) return name test(11 ) test("aa" ) 11 aa
指定类型 1 2 3 4 5 6 7 8 9 10 11 12 AA = TypeVar('AA' , int , str ) num1: AA = 1 num2: AA = "123" print (num1, num2)num3: AA = [] 1 123
Any Type
一种特殊的类型是 Any
静态类型检查器会将每种类型都视为与 Any 兼容,将 Any 视为与每种类型兼容
小栗子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from typing import Any a = None a1 = [] a2 = 2 s = '' s1 = a def foo (item: Any ) -> int : print (item) return 1 foo(a) foo(a1) foo(a2) foo(s) foo(s1)
隐式使用 Any 1 2 3 4 5 6 7 8 9 10 11 12 def legacy_parser (text ): ... return data def legacy_parser (text: Any ) -> Any : ... return data
Union 联合类型 Union[int, str] 表示既可以是 int,也可以是 str
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from typing import Union vars : Union [int , str ]vars = 1 print (vars )vars = '123' print (vars )vars = []print (vars )
等价写法 1 2 3 4 5 6 7 8 vars : Union [int , str ]vars : [int or str ]vars : Union [int ]vars : int
union 等价写法
最终 Union[int] 返回的也是 int 类型
1 Union [int , str , int ] == Union [int , str ]
重复的类型参数会自动忽略掉
1 Union [int , str ] == Union [str , int ]
自动忽略类型参数顺序
1 Union [Union [int , str ], float ] == Union [int , str , float ]
union 嵌套 union 会自动解包
Optional 可选类型
和默认参数有什么不一样
官方原话:可选参数具有默认值,具有默认值的可选参数不需要在其类型批注上使用 Optional,因为它是可选的
不过 Optional 和默认参数其实没啥实质上的区别,只是写法不同
使用 Optional 是为了让 IDE 识别到该参数有一个类型提示,可以传指定的类型和 None,且参数是可选非必传的
1 2 3 4 5 6 7 def foo (arg: int = 0 ) -> None : ... foo()
重点
Optional[int] 等价于 Union[int, None]
意味着:既可以传指定的类型 int,也可以传 None
实际栗子 1 2 3 4 5 6 7 8 9 10 11 def foo_func (arg: Optional [int ] = None ): print (arg) foo_func() foo_func(1 ) None 1
使用默认参数的写法 1 2 3 4 5 6 7 8 9 10 11 def foo_func (arg: int = None ): print (arg) foo_func() foo_func(1 ) None 1
这种写法,Pycharm 并不会 warning
重点 Optional[] 里面只能写一个数据类型
1 2 3 4 5 6 7 8 Optional [str ]Optional [List [str ]]Optional [Dict [str , Any ]]Optional [str , int ]Optional [Union [str , int , float ]]