文章目录
环境:
- Pycharm Professional 2025.1
- Python 3.12.9
Python 是一门强类型的动态类型语言,可以动态构造脚本执行、修改函数、对象类型结构、变量类型,但不允许类型不匹配的操作。
Python 也提供了类型标注功能,有了类型标注提示后,就可以在编码时即发现错误。

强类型检查的优势
- 几大优势:
- 易读:比 docstring 更易理解接口协议,也更易使用第三方库(IDE 工具支持)
- 排错:编码、编译期间即可发现错误(IDE 工具支持)
- 重构:接口范围明确,更易于理解和放心重构。
- 性能:(可能)可以做到静态编译优化。
- 现实:
- 生态-IDE:(方类型检查工具比较全面(PyCharm、VSCode、VIM 等)。
- 生态库工具:(方库有官方或大厂支持且很流行。
- 官方 /mypy-9.5 K star、Facebook/.pyre-5 K Star,Google/pytype-3 K star 等。
- 成熟度:静态检查已被广泛验证有效性,尤其大型工程(dropboxi 迁移 400 万行到静态标注)
- 行业:主流编程语言以静态强类型检查为主(C++、Jva、Go 等);其他动态语言的静态类型扩展迅猛发展(如 TypeScript)。
如下是一个非常长的(14个)PEP(Python 改进建议)的列表和落地情况(图中时间是文档时间 +1 年左右是实际落地时间),并且在 PEP 483 开始快速迭代(图中橙色是比较重要的迭代),并且到了 Python3.7 才真正算是成型。

什么时候建议采用强类型检查
提供 SDK、库/接口给其他人时。
比 docstring 更清晰、主流 IDE 支持提示和校验。
代码行数越多,价值越大。
规范化编码,通过工具可以辅助发现潜在 BUG。
需要写 UT(单元测试)的地方,就需要类型检查(by Bernat Gabor)。
以下情况适当考虑
- 原型(Prototype)或验证性质项目(POC)的代码,可以先不引入。
- 大量旧有代码,需要逐步阶段性引入(参考 Dropbox 经验)。
- 不熟悉 Python 和类型提供功能用法时,可以先不引入。
1 方法标注
1.1 参数与返回值
对函数参数,返回值进行标注。

def add(v1: int, v2: int) -> int:
"""两个数字相加"""
return v1 + v2
1.2 变参类型
对变参、命名变参直接标注其类型。

def foo(*args: str, **kwds: int):
...
foo('a', 'b', 'c')
foo('a', 'b', 1) # 错误
foo(x=1, y=2)
foo(x=1, y='x') # 错误
foo('', z=0)
1.3 函数类型
函数类型:Callable[[参数1类型, 参数2类型, ...], 返回类型]

2 数据类型
2.1 内置类型
对内置类型标注,在变量声明后面添加 :类型
即可。

a: int = 100
b: str = "abc"
2.2 复杂数据结构
Sequence
Sequence
表示server
可以是任何序列类型(包括list
,tuple
,collections.deque
等)。Sequence
更通用,允许调用方传入列表、元组或其他序列类型。list
表示server
必须是一个具体的列表(list
),不能是元组或其他序列类型。

from collections.abc import Sequence
ConnectionOptions = dict[str, str]
Address = tuple[str, int]
Server = tuple[Address, ConnectionOptions]
def connect(message: str,
server: Sequence[Server]) -> None:
...
# 等价于
def connect1(message: str,
server: Sequence[tuple[tuple[str, int], dict[str, str]]]) -> None:
...
2.3 类别选择
- 使用
Union
定义可能的类型选择:x、y、返回值的类型同一时刻可以不同。 - 使用
TypeVar
定义一组类型:x、y、返回值的类型同一时刻必须相同。

from typing import Union, TypeVar, Text
AnyStr1 = Union[Text, bytes]
AnyStr2 = TypeVar('AnyStr2', Text, bytes)
def concat1(x: AnyStr1, y: AnyStr1) -> AnyStr1:
pass
def concat2(x: AnyStr2, y: AnyStr2) -> AnyStr2:
pass
concat1('a', 'b') # 正确
concat1(b'a', b'b') # 正确
concat1('a', b'b') # 正确
concat2('a', 'b') # 正确
concat2(b'a', b'b') # 正确
concat2('a', b'b') # 错误
2.4 泛型
使用 TypeVar
表示泛型。
容器泛型示例 1

T = TypeVar('T') # 一个泛型类型
# 接受一个元素类型都是T的序列,返回值的类型也是T
def first(l: Sequence[T]) -> T: # 泛型函数
return l[0]
first([1, 2]) + 100 # 正确
first(['a', 'c']).upper() # 正确
first(['a', 'c']) + 100 # 错误
容器泛型示例 2

from typing import TypeVar, Iterable
T = TypeVar('T', bound=float)
Vector = Iterable[tuple[T, T]]
def inproduct(v: Vector[T]) -> T:
return sum(x * y for x, y in v)
类与泛型

from typing import TypeVar, Generic
T = TypeVar('T')
class MyClass(Generic[T]):
def meth_1(self, x: T) -> T:
...
def meth_2(self, x: T) -> T:
...
a: MyClass[int] = MyClass()
a.meth_1(1) # 0K
a.meth_2('a') # 错误
3 标注方式
3.1 注释标注
- 使用注释
# type: xxxx
进行标注 - 函数的标注方式:
(参数类型列表)->返回值类型

from typing import List
class A(object):
def __init__(self):
# type: ()->None
self.elements = [] # type: List[int]
def add(self, element):
# type: (List[int])->None
self.elements.append(element)
3.2 文件标注
- 独立的 stub 文件(.pyi)与源文件并行即可(放在同一目录下)。
- 优点:
- 不需要修改源代码(减少引入 BUG 可能)
- Pyi 可以使用最新的语法(源文件可以是低版本)
- 测试友好。
- 不拥有的三方库,也可以补充标注信息。
- 缺点:
- 代码重复写了一遍头(工作量)
- 打包变得复杂

# pyi文件
from typing import List
class A(object):
elements = ... # type: List[int]
def __init__(self) -> None:
...
def add(self, element: int) -> None:
...
4 特殊情形
4.1 前置引用
二叉树节在进行类型标注时需要引用自己,这种情况下需要使用前置引用,方式是用字符串代替类型标注。

from typing import List
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
def leaves(self) -> List['Tree']:
...
4.2 函数标注扩展
- 使用
overload
进行参数返回值描述。 - 必须有一个无修饰版本做真正实现。
- 仅用于静态类型检查,运行时重载可以使用 functools.singleddispatch 等。

from fontTools import unicode
from typing import overload
@overload
def utf8(value: None) -> None:
...
@overload
def utf8(value: bytes) -> bytes:
...
@overload
def utf8(value: unicode) -> bytes:
...
def utf8(value):
... # 实际的实现,必须有
有时也可以用 TypeVar
代替(更简洁)。

from typing import TypeVar
AnyStr = TypeVar('AnyStr', None, unicode, bytes)
AnyStrRet = TypeVar('AnyStrRet', None, unicode, bytes)
def utf8(value: AnyStr) -> AnyStrRet:
... # 实际实现
4.3 协变与逆变
协变:
让一个粗粒度接口(或委托)可以接收一个更加具体的接口(或委托)作为参数(或返回值);
例如:老鹰列表赋值给鸟列表。逆变:
让一个接口(或委托)的参数类型(或返回值)类型更加具体化,也就是参数类型更强,更明确。
例如:鸟列表赋值给老鹰列表。

from typing import TypeVar, Generic
T_co = TypeVar('T_co', covariant=True) # 协变
class MyList(Generic[T_co]):
def __init__(self, items: List[T_co]) -> None:
self.items = items
class Bird:
...
class Eagle(Bird):
...
egls: MyList[Eagle] = MyList([Eagle()])
brds: MyList[Bird] = egls
4.4 dataclass
- 带默认值的可变命名元组

from dataclasses import dataclass, field
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_on_hand: int = 0 # 默认值
- 复杂域默认值

@dataclass
class C:
mylist: List[int] = field(default_factory=list)
c = C()
c.mylist += [1, 2, 3]
- 延迟初始化

@dataclass
class C:
a: float
b: float
c: float = field(init=False) # 默认不初始化 c
def __post_init__(self): # 通过延迟初始化 c
self.c = self.a + self.b
print(C(1, 2)) # print: "C(a=1, b=2, c=3)"
5 高级内容
5.1 接口
Python 支持 OOP,也支持 duck typing。在强类型检查中,duck typing 也一样适用。
例如一个函数 close_resource 接受一个参数,要求这个参数必须提供一个特定的方法 .close()
。这种需求在强类型检查时,也可以支持,称之为静态 duck typing,相关方案由 PEP 544 支持。
- 定义了类似于 Java 的接口
- 可以有实现,可以被继承。
- 可以继承多个接口,构建一个新接口。
- 实际类型检查时,不要求继承关系
- 只需要对象的所有成员方法 signature 匹配 Protocoll 即可(duck-typing)。

from typing import Protocol, Iterable
class IResource(Protocol):
def close(self) -> None:
...
class Resource: # 也可以继承IResource(非必须)
def close(self) -> None:
...
def close_all(things: Iterable[IResource]) -> None:
for t in things:
t.close()
f = open('foo.txt')
r = Resource()
close_all([f, r]) # 通过
close_all([1]) # 不通过
5.2 泛型的协变/逆变
协议 (
Protocol
) 必须被实现Box
是抽象接口,不能直接实例化,必须有一个类实现content()
方法。
协变 (
covariant=True
) 的规则Box[int]
可以赋值给Box[float]
,因为int
是float
的子类型(协变允许子类替换父类)。协变表示:如果
A
是B
的子类型,则Box[A]
是Box[B]
的子类型。int
是float
的子类型(因为所有int
都可安全当作float
使用),所以Box[int]
可赋值给Box[float]
。

from typing import TypeVar
T_co = TypeVar('T_co', covariant=True) # 支持协变或逆变
class Box(Protocol[T_co]):
def content(self) -> T_co:
...
# 具体实现
class IntBox:
def content(self) -> int:
return 42
class FloatBox:
def content(self) -> float:
return 3.14
# 正确赋值
second_box: Box[int] = IntBox() # ✅ 实现 Box[int]
box: Box[float] = second_box # ✅ 协变允许(int 是 float 的子类型)
5.3 字面量类型
不修改类型为 enum
的情况下,限定传递参数:Literal[字面量1,字面量2,]
。

from typing import Literal, Any
# 总是返回True
def validate_simple(data: Any) -> Literal[True]:
...
# 只能是这几个值
MODE = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: MODE) -> str:
...
open_helper('/some/path', 'r') # 通过
open_helper('/other/path', 'typo') # 不通过
5.4 静态检查
- 可以在静态检查时导入特定的库,运行时不做。
- 这种情况下,相关标注只能用注释或字符串(前置)方式标注。

import typing
if typing.TYPE_CHECKING:
import expensive_mod
def a_func(arg: 'expensive_mod.Someclass') -> None:
a_var = arg # type: expensive_mod.Someclass
5.5 Final 与 final
Final
在 Python 3.8 版本中引入一个扩展的标注 typeing.Final
,用于标注变量,这个内置标注非常有用,可以理解为实现了 C++ 语法 const 的静态检查作用。
- 指定变量被初始化后无法再被修改、类变量无法被子类修改。
- 声明为 Final 的类成员的变量,未初始化的,必须在
__init__
里面初始化。

from typing import Final
MAX_SIZE: Final = 9000
MAX_SIZE += 1 # 错误
class ImmutablePoint:
x: Final[int]
y: Final[int] # 错误
def __init__(self) -> None:
self.x = 1 # 未初始化y
def s(self):
self.x = 1 # x不能被重新赋值
final
另一个被引入的就是小写的 typing.final
,用于标注类,可以理解为实现了 Java 语法 final 的静态检查作用。虽然 Python 本身就可以扩展实现运行时 final 的作用,但是这里实现了检查期的final,这个官方版本可以说非常的有用。
如下,除了可以修饰类(不能被继承),甚至可以修饰类的方法(不能被重写)

from typing import final
@final
class Base:
...
class Derived(Base): # 错误
...
class Base2:
@final
def foo(self):
...
class Derived2(Base2):
def foo(self): # 错误
...
5.6 关闭静态类型检查
特定情况下,我们也需要关闭静态检查(例如测试或开发中间时),这种情况只需要使用 typing.no_type_check
修饰函数或类来关闭。
如果希望关闭对一个装饰器的静态检查的话,需要使用 typing.no_type_check_decorator
修饰装饰器来关闭。

from typing import no_type_check, no_type_check_decorator
@no_type_check
def add(v1: int, v2: int) -> int:
...
@no_type_check_decorator
def log_enter_exit(fn):
def __wrapped(*args: int, **kwargs: int):
...
return __wrapped
工具参考
- typeshed:Python 内置标准和三方库的 pyi 集合 repo(PyCharm、mypy、pytype 已包含)
- mypy:官方标准静态类型检查工具。
- pyre:Facebook 开源的静态类型检查工具。
- pytype:google 开源的 Python 静态代码扫描工具(不依赖标注)。
- pyannotate:dropbox 开源的自动给 Python 添加类型标注的工具。
- pydantic:一个基于标注的 Python 数据校验与配置管理库。