虽然 Python 作为动态语言具有极高的灵活性,编程非常便利,但是为了让程序更加清晰可读,有必要加入类型注解,要求 Python 3.9+,一些关于类型注解需要的内容在 typing 模块,下面有的语句在高版本中可以省略导入typing模块。

类型注解不是强制的,并不会真正影响Python脚本的执行,但是静态代码检查可以基于类型注解提供警告,以提高编程的可靠性。这种类型检查的使用也很繁琐,容易误报警告,过度使用类型注解会丧失Python的灵活性。

变量注解

在定义变量时可以注解它的类型,这对静态代码分析很有帮助,例如

1
2
age: int = 20
age = '20'

第二行会标红:静态检查不通过,但是代码仍然可以正常执行。

注意:

  • 在 VScode+jupyter 中,只有处于同一个 cell 的代码才会执行静态检查,不同 cell 之间不会进行检查。
  • 这种变量类型注解写错了可能找不出来,例如 age: float = 20,这不会影响程序执行效果,但是会误导静态分析。

例如几种基本的类型

1
2
3
4
a: int = 3
b: float = 3.14
c: str = 'abc'
d: bool = False

对于列表,字典,元组等复合类型,直接使用 listdict 不能体现其中元素的类型,可以使用下面的形式(注意是中括号,并且要求 Python 版本很高)

1
2
3
a: list[int] = [1, 2, 3]
b: dict[str, int] = {"a": 2}
c: tuple[int, int] = (1, 2)

注意 list 支持不同的类型元素,可以使用如下语法

1
2
from typing import Union
a: list[Union[int,str]] = [1, 'a', 2]

或者对于 Python 3.10+,可以直接使用

1
a: list[int|str] = [1, 'a', 2]
1
2
3
4
def mix(scores: list[int], ages: dict[str, int]) -> tuple[int, int]:
return (0, 0)

mix([1,2,3],{'a':2})

函数注解

对函数的注解包括两部分:参数注解和函数返回值注解,对函数的注解是最常用的需求。

几个简单的例子如下

1
2
3
4
5
6
7
8
def do_nothing() -> None:
pass

def say_hi(name: str) -> str:
return f'Hello {name}!'

def add(first: int = 10, second: float = 5.5) -> float:
return first + second

关于函数返回值的注解,考虑几个特殊情形:

  • 可选返回值,返回某个类型或者 None,例如

    1
    2
    3
    4
    5
    6
    7
    from typing import Optional

    def foo(a: int = 0) -> Optional[str]:
    if a == 1:
    return 'Yeah'
    else:
    return None # 或者省略,隐式返回None

  • 多个返回值,可能返回某几个类型之一,例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from typing import Union

    def foo(a: int = 0) -> Union[str, int, float]:
    if a == 1:
    return 'h'
    elif a == 2:
    return 1
    else:
    return 1.0

  • 无返回值,注意默认情形下 Python 的函数返回 None,无返回值只用于必然引起异常的情形,例如

    1
    2
    3
    4
    from typing import NoReturn

    def hello() -> NoReturn:
    raise RuntimeError('oh no')

可调用对象注解

对于可调用对象(函数,自定义类等)自身,可以使用 Callable 类型注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from typing import Callable

class Foo:
def __call__(self):
print('I am foo')

def bar():
print('I am bar')


def hello(a: Callable[[],None]):
a()

# 类型检查通过
f = Foo();hello(f)

# 类型检查通过
hello(bar)

可以指定可调用对象的细节,包括参数类型和返回值类型,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from typing import Callable


def func1(g: Callable[[], str]) -> None:
# 要求g可调用,无参数,返回值为str
pass


def func2(
g1: Callable[[int], None], g2: Callable[[int, Exception], None]
) -> None:
# 要求g1可调用,参数为int,返回值为None
# 要求g2可调用,参数为int和Exception,返回值为None
pass

可以如下使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def t1() -> str:
return 'a'

def t2(a: int) -> None:
print(f"{a=}")

def t3(a: int,b:Exception):
pass

# 类型检查通过
func1(t1)

# 类型检查通过
func2(t2,t3)

补充

自定义类型注解

在类型注解时可以使用自定义的类型,例如

1
2
3
4
5
6
7
class Person:
def __init__(self, name: str):
self.name = name


def hello(p: Person) -> str:
return f'Hello, {p.name}'

有时类的定义尚未实现,或者为了避免循环依赖,可以使用同名的字符串代替

1
2
3
4
5
6
7
def hello(p: 'Person') -> str:
return f'Hello, {p.name}'


class Person:
def __init__(self, name: str):
self.name = name

万能注解Any

万能的 Any 类型注解,用在不知道写什么注解的时候,例如

1
2
3
4
from typing import Any

def foo() -> Any:
pass