大家好,我是正在实战各种 AI 项目的程序员晚枫。

Python 的类型提示,不只是"写给 IDE 看的注释"——它是大型项目质量保证的核心工具,也是现代 Python 编程的必备技能。

《流畅的Python(第2版)》用了整整两章(第 8、15 章)来讲类型提示,本讲带你把核心概念一次搞清楚。


🎯 为什么需要类型提示?

1
2
3
4
5
6
7
# 没有类型提示:不知道 items 里装的是什么,返回什么
def process(items):
return [item.upper() for item in items]

# 有类型提示:一目了然,IDE 可以智能补全,mypy 可以静态检查
def process(items: list[str]) -> list[str]:
return [item.upper() for item in items]

类型提示的核心价值:

  • IDE 智能补全更精准
  • 静态检查(mypy、pyright)提前发现类型错误
  • 文档即代码,函数签名就是最好的说明文档
  • 重构更安全,改了类型 IDE 立刻提示哪里出错

🔑 基础类型注解

Python 3.9+ 内置容器直接用小写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Python 3.9+ 推荐写法(不需要从 typing 导入)
def greet(name: str) -> str:
return f"Hello, {name}"

def process_scores(scores: list[int]) -> dict[str, int]:
return {"max": max(scores), "min": min(scores)}

def get_user(user_id: int) -> tuple[str, int]:
return ("张三", 25)

# Python 3.8 及以下需要从 typing 导入
from typing import List, Dict, Tuple
def process_scores_old(scores: List[int]) -> Dict[str, int]:
...

Optional 和 Union

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from typing import Optional, Union

# Optional[X] 等价于 Union[X, None]
def find_user(user_id: int) -> Optional[str]:
"""可能返回 None"""
if user_id > 0:
return f"User_{user_id}"
return None

# Python 3.10+ 可以用 X | Y 语法
def parse_value(value: str | int | float) -> str:
return str(value)

# 旧写法
def parse_value_old(value: Union[str, int, float]) -> str:
return str(value)

函数类型注解

1
2
3
4
5
6
7
from typing import Callable

# 接受回调函数的高阶函数
def apply_twice(func: Callable[[int], int], value: int) -> int:
return func(func(value))

result = apply_twice(lambda x: x * 2, 3) # 12

🔧 泛型(Generics)

TypeVar:类型变量

1
2
3
4
5
6
7
8
9
10
11
12
from typing import TypeVar

T = TypeVar('T') # 任意类型
S = TypeVar('S', str, bytes) # 只能是 str 或 bytes

def first(items: list[T]) -> T:
"""返回列表第一个元素,保持类型一致"""
return items[0]

# mypy 知道:
# first([1, 2, 3]) 返回 int
# first(["a", "b"]) 返回 str

Generic 类:泛型类

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
26
27
28
29
30
31
32
33
34
35
from typing import Generic, TypeVar

T = TypeVar('T')

class Stack(Generic[T]):
"""类型安全的栈"""

def __init__(self) -> None:
self._items: list[T] = []

def push(self, item: T) -> None:
self._items.append(item)

def pop(self) -> T:
if not self._items:
raise IndexError("Stack is empty")
return self._items.pop()

def peek(self) -> T:
if not self._items:
raise IndexError("Stack is empty")
return self._items[-1]

def __len__(self) -> int:
return len(self._items)


# 使用时指定类型
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop()) # 2

# mypy 会报错:
# int_stack.push("hello") # error: Argument 1 to "push" has incompatible type "str"; expected "int"

Python 3.12 新语法:泛型简写

1
2
3
4
5
6
7
8
9
10
# Python 3.12+ 可以更简洁地定义泛型
def first[T](items: list[T]) -> T:
return items[0]

class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []

def push(self, item: T) -> None:
self._items.append(item)

🦆 结构化子类型:Protocol

这是《流畅的Python(第2版)》重点讲解的内容——用 Protocol 实现静态鸭子类型

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
26
27
28
29
30
31
32
33
34
35
36
from typing import Protocol, runtime_checkable

# 定义协议(描述"行为接口",不要求继承)
@runtime_checkable # 加这个才能用 isinstance()
class Drawable(Protocol):
def draw(self) -> None: ...
def resize(self, factor: float) -> None: ...


class Circle:
def draw(self) -> None:
print("Drawing circle")

def resize(self, factor: float) -> None:
self.radius *= factor


class Square:
def draw(self) -> None:
print("Drawing square")

def resize(self, factor: float) -> None:
self.side *= factor


def render(shape: Drawable) -> None:
"""接受任何实现了 Drawable 协议的对象"""
shape.draw()


# Circle 和 Square 都没有继承 Drawable,但都满足协议
render(Circle()) # Drawing circle ✅
render(Square()) # Drawing square ✅

# 运行时检查(需要 @runtime_checkable)
print(isinstance(Circle(), Drawable)) # True

Protocol vs ABC

特性Protocol(结构子类型)ABC(名义子类型)
是否需要继承❌ 不需要✅ 需要
检查方式鸭子类型(看结构)继承检查
适用场景第三方类、无法修改的类内部框架、强制约束
Python 版本3.8+早期就有

🛡️ 类型守卫与类型收窄

1
2
3
4
5
6
7
8
9
10
11
12
from typing import TypeGuard

def is_string_list(val: list[object]) -> TypeGuard[list[str]]:
"""类型守卫:告诉 mypy 这个函数的返回值能窄化类型"""
return all(isinstance(x, str) for x in val)


def process(items: list[object]) -> None:
if is_string_list(items):
# 这里 mypy 知道 items 是 list[str]
for item in items:
print(item.upper()) # ✅ mypy 不报错

📦 实战:类型提示 + 运行时验证

结合 dataclasses 和类型提示,写出简洁安全的数据类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from dataclasses import dataclass, field
from typing import ClassVar

@dataclass
class Config:
"""应用配置"""
host: str
port: int = 8080
debug: bool = False
tags: list[str] = field(default_factory=list)

# ClassVar 标注类变量(不参与实例化)
MAX_CONNECTIONS: ClassVar[int] = 100

def __post_init__(self) -> None:
if not (1 <= self.port <= 65535):
raise ValueError(f"端口号无效:{self.port}")
if not self.host:
raise ValueError("host 不能为空")


cfg = Config(host="localhost", port=8080, tags=["api", "v2"])
print(cfg) # Config(host='localhost', port=8080, debug=False, tags=['api', 'v2'])

🔍 mypy 静态检查

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装
pip install mypy

# 基础检查
mypy your_code.py

# 严格模式(推荐)
mypy --strict your_code.py

# 常用选项
mypy --ignore-missing-imports # 忽略第三方库缺失的类型
mypy --show-error-codes # 显示错误代码
mypy --pretty # 更好看的输出

配置文件 mypy.ini(推荐):

1
2
3
4
5
6
7
[mypy]
python_version = 3.11
strict = True
ignore_missing_imports = True

[mypy-requests.*]
ignore_missing_imports = True

💡 实战技巧

1. 用 TYPE_CHECKING 避免循环导入

1
2
3
4
5
6
7
8
9
from __future__ import annotations  # 让所有注解延迟求值(Python 3.10+ 默认)
from typing import TYPE_CHECKING

if TYPE_CHECKING:
# 只在类型检查时导入,运行时不执行,避免循环导入
from .models import UserModel

def get_user(user_id: int) -> "UserModel":
...

2. FinalLiteral 标注常量

1
2
3
4
5
6
7
8
9
10
from typing import Final, Literal

MAX_SIZE: Final = 100 # 标记为常量,mypy 会阻止修改

def set_direction(direction: Literal["left", "right", "up", "down"]) -> None:
"""只接受特定字符串值"""
...

set_direction("left") # ✅
set_direction("diagonal") # mypy 报错

3. overload 处理重载函数

1
2
3
4
5
6
7
8
9
10
11
from typing import overload

@overload
def process(x: int) -> int: ...
@overload
def process(x: str) -> str: ...

def process(x):
if isinstance(x, int):
return x * 2
return x.upper()

⚠️ 常见陷阱

1. 类型提示不影响运行时行为

1
2
3
4
5
6
def add(a: int, b: int) -> int:
return a + b

# Python 不会在运行时检查类型!
add("hello", " world") # 运行时不报错,返回 "hello world"
# 只有 mypy 这样的静态检查工具才会报错

2. 可变默认值用 field(default_factory=...)

1
2
3
4
5
6
7
8
9
10
11
from dataclasses import dataclass, field

# ❌ 错误
@dataclass
class Bad:
items: list[int] = [] # mypy 和 dataclass 都会报错

# ✅ 正确
@dataclass
class Good:
items: list[int] = field(default_factory=list)

3. list vs Sequence:选择合适的抽象

1
2
3
4
5
6
7
8
9
from typing import Sequence, MutableSequence

# 如果函数只读取,用 Sequence(更宽泛,tuple/list 都可以传)
def read_only(items: Sequence[int]) -> int:
return sum(items)

# 如果函数需要修改,用 MutableSequence 或 list
def append_item(items: list[int], value: int) -> None:
items.append(value)

🎯 本讲总结

基础注解:Python 3.9+ 可直接用 list[int]dict[str, int]Optional[X] 等价于 X | None(3.10+)。

泛型TypeVar + Generic 实现类型安全的通用容器;Python 3.12+ 有更简洁的泛型语法。

Protocol:结构子类型,实现静态鸭子类型——不用继承,只看是否实现了需要的方法。

mypy--strict 严格模式是推荐配置;用 mypy.ini 管理项目级配置。

实用技巧TYPE_CHECKING 避免循环导入;Final/Literal 标注常量;overload 处理多态。

关键原则:类型提示不影响运行时,它只服务于静态分析工具和 IDE。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》

学习路线: 零基础 → 《从入门到实践》 → 《流畅的 Python》 → 本门课程 → 《CPython 设计与实现》


🎓 加入《流畅的 Python》直播共读营

学到这里,如果你想系统吃透这本书——欢迎加入我的直播共读课。

  • 每周直播精讲,逐章拆解核心知识点
  • 专属学习群,随时答疑交流
  • 试运营特惠:499 元299 元

👉 【立即报名《流畅的 Python》共读课】https://mp.weixin.qq.com/s/ivHJwn1nNx5ug4TFrapvGg

🔗 课程导航

上一讲:类元编程 | 下一讲:并发编程模型


💬 联系我

平台账号/链接
微信扫码加好友
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!

fluent-python.png