4733 字
24 分钟

Python 类型注解(Type Hints)完全指南:从基础标注到 Pydantic 数据验证 | Python 进阶核心知识

类型注解(Type Hints)是现代 Python 最重要的特性之一。自 Python 3.5 引入以来,类型注解已经从一个可选的「语法糖」发展为企业级 Python 开发的标准实践——它不改变运行时行为,却能让代码更可读、更安全、更易于维护。

Python 类型注解示意图

本文将从零开始,系统讲解 Python 类型注解的完整知识体系,包括:

  • 基础类型标注与变量注解
  • Optional / Union / None 可选类型
  • 泛型与 TypeVar 类型变量
  • CallableTypedDictNamedTuple 高级类型
  • Protocol 结构化子类型(鸭子类型的类型安全版)
  • mypy 静态类型检查实战
  • Pydantic 数据验证与模型定义
  • FastAPI 中的类型注解集成
  • 常见陷阱与最佳实践

一、类型注解基础#

1.1 为什么要用类型注解?#

先看一段没有类型注解的代码:

def process(data):
result = []
for item in data:
result.append(item * 2)
return result

这段代码有什么问题?data 是什么类型?item 是什么类型?返回值是什么类型?不读完整段代码根本无法回答。

加上类型注解后:

def process(data: list[int]) -> list[int]:
result: list[int] = []
for item in data:
result.append(item * 2)
return result

一目了然:接收整数列表,返回整数列表。类型注解的核心价值不在于约束运行时行为,而在于提升代码可读性和开发体验

1.2 变量注解#

# 基础变量注解
name: str = "Alice"
age: int = 30
height: float = 1.75
is_active: bool = True
# 容器类型注解(Python 3.9+ 可直接使用小写类型)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 90, "Bob": 85}
point: tuple[int, int] = (10, 20)
unique_ids: set[int] = {1, 2, 3}
# 无需赋值,仅声明类型(用于模块级或类级常量)
MAX_CONNECTIONS: int
DEFAULT_TIMEOUT: float = 30.0

1.3 函数注解#

# 参数注解和返回值注解
def greet(name: str, times: int = 1) -> str:
return f"Hello, {name}! " * times
# 多返回值注解(返回元组)
def get_user_info(user_id: int) -> tuple[str, int, bool]:
return ("Alice", 30, True)
# 无返回值
def log_message(msg: str) -> None:
print(f"[LOG] {msg}")
# 异步函数注解
async def fetch_data(url: str) -> dict[str, str]:
# ...
return {"status": "ok"}

二、Optional 与 Union:可选类型#

2.1 Optional:可能为 None 的值#

Optional[X] 等价于 X | None,表示值可以是 X 类型或 None

from typing import Optional
# Optional[str] 表示 name 可以是字符串或 None
def find_user(user_id: int) -> Optional[str]:
users = {1: "Alice", 2: "Bob"}
return users.get(user_id) # .get() 找不到时返回 None
result = find_user(1) # "Alice"
result = find_user(99) # None
# Python 3.10+ 新语法:X | None(推荐)
def find_user_new(user_id: int) -> str | None:
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)

2.2 Union:多类型联合#

Union[X, Y] 表示值可以是多种类型之一:

from typing import Union
# 值可以是 int 或 str
def process_id(value: Union[int, str]) -> str:
if isinstance(value, int):
return f"ID-{value:04d}"
return value.upper()
# Python 3.10+ 新语法:X | Y(推荐)
def process_id_new(value: int | str) -> str:
if isinstance(value, int):
return f"ID-{value:04d}"
return value.upper()
# 多类型联合
def parse_value(value: int | float | str | None) -> float | None:
if value is None:
return None
return float(value)

2.3 Optional 的常见误区#

# ❌ 错误:Optional[list] 表示列表本身可能为 None
# 并不表示列表内的元素可能为 None
def bad_example(data: Optional[list[int]]) -> int:
if data is None:
return 0
return sum(data)
# ✅ 正确:如果列表内元素也可能为 None
from typing import Optional
def good_example(data: list[Optional[int]] | None) -> int:
if data is None:
return 0
return sum(x for x in data if x is not None)
# ✅ 更清晰:使用 | None 语法
def clear_example(data: list[int | None] | None) -> int:
if data is None:
return 0
return sum(x for x in data if x is not None)

三、泛型与 TypeVar:类型变量#

3.1 为什么需要 TypeVar?#

考虑一个简单的函数——返回列表的第一个元素:

# 没有泛型:返回值类型丢失
def first(items: list) -> ???:
return items[0]
# 用 Any:等于没有类型注解
from typing import Any
def first(items: list[Any]) -> Any:
return items[0]

first([1, 2, 3]) 返回 intfirst(["a", "b"]) 返回 str——返回类型取决于输入类型。TypeVar 就是用来表达这种类型关联的:

from typing import TypeVar
T = TypeVar('T')
def first(items: list[T]) -> T:
return items[0]
# 现在 mypy 知道:
result1 = first([1, 2, 3]) # result1 的类型是 int
result2 = first(["a", "b", "c"]) # result2 的类型是 str

3.2 限制类型范围的 TypeVar(bounded)#

from typing import TypeVar
# T 必须是 Number 的子类
from numbers import Number
N = TypeVar('N', bound=Number)
def sum_values(values: list[N]) -> N:
total: N = values[0]
for v in values[1:]:
total = total + v
return total
result = sum_values([1, 2, 3]) # 返回 int
result = sum_values([1.5, 2.5, 3.5]) # 返回 float
# ❌ 错误:str 不是 Number 的子类
# result = sum_values(["a", "b"])

3.3 枚举类型的 TypeVar(constraints)#

from typing import TypeVar
# T 只能是 str 或 bytes
S = TypeVar('S', str, bytes)
def longest(a: S, b: S) -> S:
return a if len(a) >= len(b) else b
result1 = longest("hello", "world") # 返回 str
result2 = longest(b"hello", b"world") # 返回 bytes
# ❌ 错误:int 不在允许的范围内
# result3 = longest(123, 456)

3.4 泛型类#

from typing import TypeVar, Generic
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("pop from empty stack")
return self._items.pop()
def peek(self) -> T:
if not self._items:
raise IndexError("peek from empty stack")
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)
value: int = int_stack.pop() # mypy 知道返回 int
str_stack: Stack[str] = Stack()
str_stack.push("hello")
text: str = str_stack.pop() # mypy 知道返回 str

3.5 用户自定义泛型函数实战#

from typing import TypeVar, K, V
K = TypeVar('K')
V = TypeVar('V')
def invert_dict(d: dict[K, V]) -> dict[V, K]:
"""反转字典的键和值"""
return {v: k for k, v in d.items()}
def merge_dicts(d1: dict[K, V], d2: dict[K, V]) -> dict[K, V]:
"""合并两个字典,后者的值优先"""
result: dict[K, V] = {}
result.update(d1)
result.update(d2)
return result
# 使用
original = {"Alice": 90, "Bob": 85}
inverted = invert_dict(original) # {90: "Alice", 85: "Bob"}

四、Callable 与函数类型#

4.1 基础 Callable#

from typing import Callable
# Callable[[参数类型...], 返回类型]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
# 使用
result = apply(lambda x, y: x + y, 3, 5) # 8
result = apply(lambda x, y: x * y, 3, 5) # 15
# 无参数的回调
def run_callback(callback: Callable[[], None]) -> None:
callback()
# 可变参数的回调(不推荐,但有时需要)
from typing import Any
def flexible_call(func: Callable[..., Any], *args: Any) -> Any:
return func(*args)

4.2 函数类型作为参数和返回值#

from typing import Callable
# 高阶函数:接收函数,返回函数
def multiply_by(factor: int) -> Callable[[int], int]:
"""返回一个将输入乘以 factor 的函数"""
def multiplier(x: int) -> int:
return x * factor
return multiplier
double = multiply_by(2)
triple = multiply_by(3)
print(double(5)) # 10
print(triple(5)) # 15
# 策略模式:用 Callable 类型实现
type SortStrategy = Callable[[list[int]], list[int]]
def sort_ascending(data: list[int]) -> list[int]:
return sorted(data)
def sort_descending(data: list[int]) -> list[int]:
return sorted(data, reverse=True)
def process_data(data: list[int], strategy: SortStrategy) -> list[int]:
return strategy(data)
result1 = process_data([3, 1, 2], sort_ascending) # [1, 2, 3]
result2 = process_data([3, 1, 2], sort_descending) # [3, 2, 1]

4.3 type 语句(Python 3.12+)#

Python 3.12 引入了 type 语句,让类型别名更清晰:

# Python 3.12+ 新语法
type Vector = list[float]
type Matrix = list[Vector]
# 旧语法
from typing import TypeAlias
Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[Vector]
# 泛型类型别名(3.12+)
type ListOrSet[T] = list[T] | set[T]
def unique_items(data: ListOrSet[int]) -> list[int]:
if isinstance(data, list):
return list(set(data))
return list(data)

五、TypedDict 与 NamedTuple#

5.1 TypedDict:带类型的字典#

当函数接收或返回字典时,普通的 dict[str, Any] 无法描述键值对的类型。TypedDict 解决了这个问题:

from typing import TypedDict
# 定义带类型的字典结构
class UserInfo(TypedDict):
name: str
age: int
email: str
is_active: bool
# 使用:就像普通字典,但 IDE 会提示键名和类型
def create_user(name: str, age: int) -> UserInfo:
return {
"name": name,
"age": age,
"email": f"{name.lower()}@example.com",
"is_active": True,
}
user: UserInfo = create_user("Alice", 30)
print(user["name"]) # "Alice"
print(user["age"]) # 30
# mypy 会检查键是否完整、类型是否匹配
# user = {"name": "Bob"} # ❌ mypy 报错:缺少 age, email, is_active

5.2 TypedDict 的高级用法#

from typing import TypedDict, Required, NotRequired
# Python 3.11+:可选键
class ApiConfig(TypedDict):
host: str
port: int
# 可选键:可以不存在
timeout: NotRequired[float]
# 必填键(用于覆盖 total=False 的默认行为)
api_key: Required[str]
# 使用
config: ApiConfig = {
"host": "localhost",
"port": 8080,
"api_key": "secret",
# timeout 可以不提供
}
# 函数式定义(不推荐,可读性差)
from typing import TypedDict
Movie = TypedDict("Movie", {"title": str, "year": int, "rating": float})
movie: Movie = {"title": "Inception", "year": 2010, "rating": 8.8}

5.3 NamedTuple:带类型的命名元组#

from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
label: str = "origin" # 带默认值
# 使用:既可按位置访问,也可按名称访问
p = Point(3.0, 4.0, "A")
print(p.x) # 3.0
print(p[0]) # 3.0
print(p.label) # "A"
# 不可变
# p.x = 5.0 # ❌ AttributeError
# 解包
x, y, label = p
# 作为函数返回值(比 tuple 更清晰)
def parse_coordinates(text: str) -> Point:
parts = text.split(",")
return Point(float(parts[0]), float(parts[1]), parts[2])

5.4 NamedTuple vs dataclass vs TypedDict#

from typing import NamedTuple, TypedDict
from dataclasses import dataclass
# NamedTuple:不可变,轻量,可解包
class PointNT(NamedTuple):
x: float
y: float
# dataclass:可变(默认),功能更丰富
@dataclass
class PointDC:
x: float
y: float
def distance_to_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
# TypedDict:用于字典数据,不是类
class PointTD(TypedDict):
x: float
y: float
特性NamedTupledataclassTypedDict
可变性不可变可变(默认)可变(字典)
方法支持有限完整
解包支持不支持不支持
序列化需手动asdict()天然是 dict
适用场景轻量数据数据模型API/JSON

六、Protocol:结构化子类型#

6.1 鸭子类型的类型安全版#

Python 的哲学是「鸭子类型」——如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。Protocol 让鸭子类型拥有了类型安全:

from typing import Protocol
# 定义协议(接口)
class SupportsClose(Protocol):
def close(self) -> None:
...
class SupportsRead(Protocol):
def read(self, size: int = -1) -> str:
...
# 任何有 close() 方法的对象都满足 SupportsClose
# 不需要显式继承
def cleanup(resource: SupportsClose) -> None:
resource.close()
# 文件对象有 close 方法,满足协议
f = open("test.txt", "w")
cleanup(f) # ✅ mypy 认可
# 自定义类只要有 close 方法就行
class CustomResource:
def close(self) -> None:
print("Custom cleanup")
cleanup(CustomResource()) # ✅ 也满足

6.2 实战:可迭代与可序列化协议#

from typing import Protocol, Iterator
class Iterable(Protocol):
def __iter__(self) -> Iterator:
...
class Serializable(Protocol):
def to_dict(self) -> dict:
...
def process_iterable(items: Iterable) -> list:
return [item for item in items]
def serialize(obj: Serializable) -> str:
import json
return json.dumps(obj.to_dict())
# 自定义类自动满足协议
class UserCollection:
def __init__(self, users: list[str]) -> None:
self.users = users
def __iter__(self) -> Iterator:
return iter(self.users)
# UserCollection 满足 Iterable 协议(有 __iter__ 方法)
process_iterable(UserCollection(["Alice", "Bob"])) # ✅

6.3 运行时检查 Protocol#

from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
def draw(self) -> str:
...
class Circle:
def draw(self) -> str:
return "Drawing circle"
class Square:
def draw(self) -> str:
return "Drawing square"
class Text:
pass # 没有 draw 方法
# 运行时检查
print(isinstance(Circle(), Drawable)) # True
print(isinstance(Square(), Drawable)) # True
print(isinstance(Text(), Drawable)) # False
# 根据协议过滤对象
def render_all(items: list) -> None:
for item in items:
if isinstance(item, Drawable):
print(item.draw())
render_all([Circle(), Square(), Text(), "string"])
# Drawing circle
# Drawing square

七、mypy 静态类型检查#

7.1 安装与基本使用#

Terminal window
# 安装 mypy
pip install mypy
# 检查单个文件
mypy my_script.py
# 检查整个项目
mypy src/
# 严格模式
mypy --strict src/

7.2 mypy 配置文件#

在项目根目录创建 mypy.ini

[mypy]
python_version = 3.12
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
no_implicit_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
# 对特定模块放宽规则
[mypy-tests.*]
disallow_untyped_defs = False
[mypy-third_party_lib.*]
ignore_missing_imports = True

7.3 mypy 常见错误与修复#

# ❌ 错误 1:返回值类型不匹配
def get_value() -> int:
return "hello" # error: Incompatible return value type (got "str", expected "int")
# ✅ 修复
def get_value() -> str:
return "hello"
# ❌ 错误 2:参数类型不匹配
def add(a: int, b: int) -> int:
return a + b
add("1", "2") # error: Argument 1 to "add" has incompatible type "str"; expected "int"
# ✅ 修复
add(1, 2)
# ❌ 错误 3:Optional 类型未处理 None
from typing import Optional
def get_name() -> Optional[str]:
return None
name = get_name()
print(name.upper()) # error: Item "None" of "Optional[str]" has no attribute "upper"
# ✅ 修复:先检查 None
if name is not None:
print(name.upper())
# ❌ 错误 4:列表类型不统一
def process(data: list[int]) -> int:
return sum(data)
process([1, "2", 3]) # error: List item 1 has incompatible type "str"; expected "int"
# ✅ 修复
process([1, 2, 3])

7.4 类型忽略与断言#

# 类型忽略:临时跳过某行的类型检查
result = some_untyped_func() # type: ignore
# 更精确的忽略(mypy 特定)
result = some_untyped_func() # type: ignore[return-value]
# cast:手动告诉 mypy 类型(不改变运行时行为)
from typing import cast, Any
raw_data: Any = get_external_data()
data = cast(dict[str, int], raw_data)
# mypy 现在认为 data 是 dict[str, int]
# TYPE_CHECKING:仅在类型检查时导入
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mymodule import HeavyClass # 运行时不导入,仅用于类型注解
def process(obj: "HeavyClass") -> None:
...

八、Pydantic:运行时数据验证#

8.1 Pydantic 基础#

与类型注解(仅静态检查)不同,Pydantic 在运行时验证数据:

from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
age: int
is_active: bool = True
# 正常创建:自动类型转换
user = User(id="123", name="Alice", email="alice@example.com", age="30")
print(user.id) # 123(字符串 "123" 自动转为 int)
print(user.age) # 30
print(user.is_active) # True(默认值)
# 序列化
print(user.model_dump())
# {'id': 123, 'name': 'Alice', 'email': 'alice@example.com', 'age': 30, 'is_active': True}
print(user.model_dump_json())
# '{"id":123,"name":"Alice","email":"alice@example.com","age":30,"is_active":true}'
# 验证失败:抛出 ValidationError
from pydantic import ValidationError
try:
bad_user = User(id="not_a_number", name="Bob", email="bob@example.com", age=25)
except ValidationError as e:
print(e.errors())
# [{'type': 'int_parsing', 'loc': ('id',), 'msg': 'Input should be a valid integer...', ...}]

8.2 字段验证与约束#

from pydantic import BaseModel, Field, EmailStr, field_validator
class Product(BaseModel):
name: str = Field(..., min_length=1, max_length=100, description="产品名称")
price: float = Field(..., gt=0, description="价格必须大于0")
quantity: int = Field(default=0, ge=0, description="库存不能为负")
tags: list[str] = Field(default_factory=list, max_length=10)
# 字段验证器
@field_validator("name")
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError("产品名称不能为空")
return v.strip()
@field_validator("price")
@classmethod
def price_must_be_reasonable(cls, v: float) -> float:
if v > 1000000:
raise ValueError("价格异常过高,请检查")
return v
# 使用
product = Product(name=" Laptop ", price=999.99, quantity=50, tags=["electronics", "computer"])
print(product.name) # "Laptop"(自动 strip)
print(product.price) # 999.99
# 验证失败示例
try:
Product(name="", price=-10, quantity=-5)
except ValidationError as e:
for error in e.errors():
print(f"{error['loc']}: {error['msg']}")

8.3 嵌套模型与复杂结构#

from pydantic import BaseModel
from datetime import datetime
from typing import Optional
class Address(BaseModel):
street: str
city: str
zip_code: str
country: str = "China"
class OrderItem(BaseModel):
product_id: int
name: str
price: float
quantity: int
class Order(BaseModel):
order_id: str
customer_name: str
items: list[OrderItem]
shipping_address: Address
created_at: datetime
note: Optional[str] = None
@property
def total(self) -> float:
return sum(item.price * item.quantity for item in self.items)
# 从 JSON 创建(模拟 API 响应)
json_data = {
"order_id": "ORD-2026-001",
"customer_name": "Alice",
"items": [
{"product_id": 1, "name": "Laptop", "price": 999.99, "quantity": 1},
{"product_id": 2, "name": "Mouse", "price": 29.99, "quantity": 2},
],
"shipping_address": {
"street": "123 Main St",
"city": "Shanghai",
"zip_code": "200000",
},
"created_at": "2026-06-19T10:30:00",
}
order = Order.model_validate(json_data)
print(order.order_id) # "ORD-2026-001"
print(order.shipping_address.city) # "Shanghai"
print(order.created_at) # 2026-06-19 10:30:00
print(order.total) # 1059.97

8.4 模型配置与方法#

from pydantic import BaseModel, ConfigDict
class Config(BaseModel):
# Pydantic v2 配置
model_config = ConfigDict(
# 允许从数据库 ORM 对象创建
from_attributes=True,
# 禁止额外字段
extra="forbid",
# 允许从字段名别名映射
populate_by_name=True,
# 大小写不敏感
str_to_lower=False,
)
host: str
port: int
debug: bool = False
# 从 ORM 对象创建
class DBConfig:
def __init__(self):
self.host = "localhost"
self.port = 5432
self.debug = True
db_obj = DBConfig()
config = Config.model_validate(db_obj)
print(config.host) # "localhost"
print(config.port) # 5432
# 模型更新(返回新实例,不改原对象)
updated = config.model_copy(update={"port": 8080})
print(updated.port) # 8080
print(config.port) # 5432(原对象不变)

九、FastAPI 中的类型注解#

FastAPI 是类型注解最佳实践的集大成者——它直接利用类型注解做参数解析、数据验证和自动文档生成:

from fastapi import FastAPI, Path, Query, HTTPException
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
# 响应模型
class UserResponse(BaseModel):
id: int
name: str
email: str
# 请求模型
class UserCreate(BaseModel):
name: str
email: str
age: int
# 路径参数 + 查询参数 + 响应模型
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(
user_id: int = Path(..., gt=0, description="用户ID必须大于0"),
include_email: bool = Query(default=False, description="是否包含邮箱"),
):
# user_id 自动从路径解析为 int
# include_email 自动从查询参数解析为 bool
user = {"id": user_id, "name": "Alice", "email": "alice@example.com"}
if not include_email:
user.pop("email")
return user
# 请求体模型
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
# user 已经过 Pydantic 验证
# 如果验证失败,FastAPI 自动返回 422 错误
return {"id": 1, "name": user.name, "email": user.email}
# 自动生成 OpenAPI 文档:/docs
# 所有类型注解自动反映到 Swagger UI

FastAPI 的类型注解带来了三重收益:

能力实现方式效果
参数解析从路径/查询/体中提取并转换无需手动 int() / bool()
数据验证Pydantic 运行时校验自动返回 422 错误
文档生成类型注解 -> OpenAPI SchemaSwagger UI 自动更新

十、常见陷阱与最佳实践#

陷阱 1:可变默认值与类型注解#

# ❌ 错误:可变默认值(经典 Python 陷阱,与类型注解无关但容易混淆)
def add_item(item: str, items: list[str] = []) -> list[str]:
items.append(item)
return items
# ✅ 正确:使用 None 作为默认值
from typing import Optional
def add_item(item: str, items: Optional[list[str]] = None) -> list[str]:
if items is None:
items = []
items.append(item)
return items

陷阱 2:过度使用 Any#

# ❌ Any 等于没有类型注解
from typing import Any
def process(data: Any) -> Any:
return data
# ✅ 尽量使用具体类型或泛型
from typing import TypeVar
T = TypeVar('T')
def process(data: T) -> T:
return data
# 如果确实不知道类型,用 object 比 Any 更好
def log_data(data: object) -> None:
print(data) # object 类型,mypy 不会报错但不允许调用任意方法

陷阱 3:类型注解不影响运行时#

# 类型注解不会在运行时阻止错误类型
def add(a: int, b: int) -> int:
return a + b
# 运行时不会报错(Python 是动态类型语言)
result = add("hello", "world") # "helloworld"
# 只有 mypy 才会发现这个错误

陷阱 4:前向引用#

# ❌ 错误:类定义中引用自身时,类还未定义完成
class Node:
def __init__(self, value: int, next: Node) -> None: # NameError: name 'Node' is not defined
self.value = value
self.next = next
# ✅ 修复 1:使用字符串引用(前向引用)
class Node:
def __init__(self, value: int, next: "Node") -> None:
self.value = value
self.next = next
# ✅ 修复 2:Python 3.11+ 使用 from __future__
from __future__ import annotations
class Node:
def __init__(self, value: int, next: Node) -> None: # 现在可以了
self.value = value
self.next = next

陷阱 5:泛型容器类型版本差异#

# Python 3.8 及更早:需要从 typing 导入
from typing import List, Dict, Tuple, Set
items: List[int] = [1, 2, 3]
mapping: Dict[str, int] = {"a": 1}
# Python 3.9+:可以直接使用内置类型(推荐)
items: list[int] = [1, 2, 3]
mapping: dict[str, int] = {"a": 1}
# Python 3.9 之前的泛型类型别名
from typing import Union, Optional
value: Optional[int] = None # 等价于 Union[int, None]
# Python 3.10+:可以直接用 | 语法(推荐)
value: int | None = None

最佳实践清单#

原则说明
从公共 API 开始公共函数/类的方法优先加注解,内部代码可逐步补充
优先使用具体类型避免 Any,用 object 或泛型替代
善用 Optional/Union明确表达可能为 None 或多类型的情况
用 Protocol 替代继承鸭子类型场景下,Protocol 比抽象基类更灵活
Pydantic 做边界验证外部数据(API/配置/数据库)入口处用 Pydantic 验证
配置 mypy strict新项目从 --strict 开始,逐步收紧类型安全
使用 from __future__ import annotations解决前向引用问题,统一类型注解风格
保持注解与代码同步修改参数时同步更新注解,避免注解过期误导

十一、总结:类型注解学习路线#

阶段掌握内容目标
入门基础类型、变量注解、函数注解能读懂带注解的代码
进阶Optional、Union、泛型、TypeVar能为复杂函数编写准确注解
高级Callable、TypedDict、Protocol、mypy能设计类型安全的 API
实战Pydantic 模型、FastAPI 集成能构建端到端类型安全的应用

类型注解不是 Python 的「可选附件」,而是现代 Python 开发的核心实践。 它让动态类型语言拥有了接近静态类型语言的安全性,同时保持了 Python 的简洁与灵活。从今天开始,给你的每一个公共函数加上类型注解——这是提升代码质量最低成本的改进。

Python 类型注解(Type Hints)完全指南:从基础标注到 Pydantic 数据验证 | Python 进阶核心知识
https://971918.xyz/posts/python-guide/python-type-hints-guide/
作者
九所长
发布于
2026-06-19
许可协议
CC BY-NC-SA 4.0