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/