1. 概述 我们都知道,Python是动态编程语言(Dynamic Programming Language)。在Python中不需要对变量进行类型声明,一个变量也可以被赋值为不同的类型。如 a = 3 # 定义a这个变量时不需要进行类型声明 a = "a string" # a一开始被定义为整数3,又可以别定义为一个字符串 与此类似的还有JavaScript,PHP等。和他们相对应的就是静态语言(Static Programming Language),如C、C++和JAVA。在静态语言中需要对变量以及函数的返回值进行严格的类型声明,如 #include int add(int x, int y) // 对函数的返回值以及参数进行了类型声明 { int result; // 新的变量也要进行类型声明 result = x + y; return result; } 而如果在Python中,就随意多了(如下代码所示),该函数不仅可以用于int + int,float + float,甚至可以用于str + str。 def add(x, y): result = x + y print(result) return result add(1, 2) # int + int, output: 3 add(1.2, 2.1) # float + float, output: 3.3 add("have a ", "try!") # str + str, output: have a try! 1.1 Python中的“类型声明”情况一:类型提示 但是在Python 3.5中加入了“类型声明”的功能,官方文档叫类型提示(type hints)[1]。这样函数就可以写成如下形式,但是在实际运行的时候你会发现一个问题:虽然做了类型提示,但是函数本身并不会对输入的参数进行类型检查。也就是说,除了类型声明的int + int情况,其他情况如float + float和str + str依然适用,并不会报错! def add(x: int, y: int) -> int: result = x + y print(result) return result # 虽然做了类型提示,但并不影响以下代码的执行 add(1, 2) # int + int, output: 3 add(1.2, 2.1) # float + float, output: 3.3 add("have a ", "try!") # str + str, output: have a try! 1.2 Python中的“类型声明”情况二:变量注解 除了以上的例子,在Python 3.6中继续加入了变量注解(variable annotations)的功能[2],变量注解的格式如下: # 以下两行是完全等价的 age: int; age = 18 age: int = 18 但是如果你进行如下赋值,你会发现,Python并不会报错,和没有进行变量注解没什么区别。 # 以下两行是完全等价的 age: int = "I am a string!" print(age) 既然类型提示和变量注解都不会对类型进行检查和报错,那意义何在呢? 2. Python中“类型声明”的意义何在? 其实在官方文档的用词中已经给出了答案,即提示(hints)和注解(annotations),也就是说这些“类型声明”仅仅起到了提示和注解的作用。这么做有三方面好处: 2.1 方便程序员阅读代码 2.2 方便IDE进行代码提示(如下图所示); code对输入参数的类型和返回值的类型进行了提示 2.3 通过mypy进行类型检查 你也可以通过mypy进行类型检查,如 # demo.py def add(x: int, y: int) -> int: result = x + y print(result) return result add("have a ", "try!") 检查方法及结果如下: $ mypy demo.py demo.py:6: error: Argument 1 to "add" has incompatible type "str"; expected "int" demo.py:6: error: Argument 2 to "add" has incompatible type "str"; expected "int" Found 2 errors in 1 file (checked 1 source file) 3. typing与“类型声明”的更多语法 typing模块中的可导出内容如下,我对其中部分内容进行具体说明。 Typing模块中的可导出内容 3.1 Tuple from typing import Tuple t1: Tuple = (1, 2, 3,) # 声明t1为元组 t2: tuple[int, ...] = (1, 2, 3,) # 声明t2为整数组成的元组 # t3: Tuple[int] = (1, 2, 3,) # 错误用法,会报错! t4: Tuple[int, int, int] = (1, 2, 3,) # 声明t2为3个整数组成的元组 t5: Tuple[int, str, float] = (1, "2", 3.1,) # 声明t5为int, str, float三个元素组成的元组 3.2 List from typing import List l1: List = [1, 2, 3] # 声明t1为元组 l2: List[int] = [1, 2, 3] # 声明l2为整数组成的元组 # l3: List[int, str, float] = [1, "2", 3.1] # 错误用法,会报错! 3.3 Dict from typing import Dict d1: Dict = {"a": 1} # 声明d1为字典 d2: Dict[str, int] = {"a": 1} # 声明d2为字典,且键为str,值为int 3.4 Set from typing import Set s1: Set = {1, 2, 3} # 声明s1为集合 s2: Set[int] = {1, 2, 3} # 声明s2为int组成的集合 3.5 Union from typing import List, Union # 声明l1为列表,且列表中的元素为int,str和float中一种 l1: List[Union[int, str, float]] = [1, "2", 3.1] 3.6 Any Any的意思是该变量可以是任何类型,包括None。既然可以是任何类型,那Any的意义何在呢?答案是:当对元组内的每个元素都做类型声明,而其中一个元素可以是任何类型的时候,Any就派上用场了。 from typing import Any, Tuple # 变量可以为任何类型,包括None a: Any = None # Used as an escape hatch l: Tuple[(int, Any, str)] = (1, None, "test") 3.7 Optional Optional常用语函数传参,代表该参数可无。 from typing import Optional # arg参数可无,若有则声明为int型 def foo(arg: Optional[int] = None) -> str: print(arg) return "Demo" foo() # output: None foo(3) # output: 3 3.8 Callable Callable指可调用类型,通常指函数,Callable[[X, Y], Z]中[X, Y]指传入参数的类型,Z指的是返回参数的类型,如 from ast import Call from typing import Callable def add(x: int, y: int) -> int: return x + y f: Callable[[int, int], int] = add 匿名函数中由于特殊的语法格式,无法进行类型声明。但是可以通过使用Callable进行类型声明,如 from ast import Call from typing import Callable # 匿名函数 add = lambda x, y: x + y # 无法直接对匿名函数中的参数和返回值进行类型声明, 会报错 # add = lambda x: int, y: int: x + y: int # 可通过Callable解决上述问题 add: Callable[[int, int], int] = lambda x, y: x + y add(1, 2) 参考 ^typing — Support for type hints https://docs.python.org/3/library/typing.html ^PEP 526 – Syntax for Variable Annotations https://peps.python.org/pep-0526/