Python 类型注解完整指南——TypeVar、Protocol、泛型在工程中的实践
2026/4/30大约 7 分钟
Python 类型注解完整指南——TypeVar、Protocol、泛型在工程中的实践
适读人群:会基本类型注解、想深入掌握泛型和 Protocol 的 Python 工程师 | 阅读时长:约 16 分钟 | 核心价值:让 Python 代码也能享受 Java 泛型的安全性和 IDE 智能提示
一次代码 review 的启发
老孙负责维护一个数据处理库,里面有大量的工具函数。有一天做代码 review,发现了这样一个函数:
def process_items(items, transform):
return [transform(item) for item in items]函数能用,但完全不知道 items 是什么类型,transform 需要什么签名,返回值是什么类型。调用方只能靠猜,IDE 也没有任何提示。
如果是 Java,这种函数早就用泛型标注清楚了:
<T, R> List<R> processItems(List<T> items, Function<T, R> transform)我告诉他,Python 可以写得一样清楚:
from typing import TypeVar, Callable
T = TypeVar("T")
R = TypeVar("R")
def process_items(items: list[T], transform: Callable[[T], R]) -> list[R]:
return [transform(item) for item in items]这篇文章,就把 Python 类型系统的高级特性系统讲一遍。
一、基础类型注解回顾
from typing import Optional, Union, Any
# 基础类型
name: str = "老张"
age: int = 35
score: float = 95.0
active: bool = True
# 集合类型(Python 3.9+ 可以直接用内置类型)
names: list[str] = ["老张", "小陈"]
scores: dict[str, float] = {"老张": 95.0}
tags: set[str] = {"python", "java"}
pair: tuple[str, int] = ("老张", 35)
# 可选类型
nickname: Optional[str] = None # 等价于 str | None
value: str | None = None # Python 3.10+ 新语法
# 联合类型
result: Union[str, int, None] = 42
result_new: str | int | None = 42 # Python 3.10+
# 任意类型(谨慎使用)
data: Any = {"whatever": True}二、TypeVar:泛型函数
2.1 基础 TypeVar
from typing import TypeVar, Sequence
T = TypeVar("T") # 无约束,任意类型
def first(items: Sequence[T]) -> T | None:
"""返回序列的第一个元素"""
return items[0] if items else None
# IDE 知道返回类型和输入类型一致
x: int | None = first([1, 2, 3]) # x 是 int | None
s: str | None = first(["a", "b"]) # s 是 str | None2.2 约束 TypeVar
from typing import TypeVar
# 约束:只能是 int 或 float
Number = TypeVar("Number", int, float)
def double(value: Number) -> Number:
return value * 2
# 绑定:只能是某个类型或其子类
Comparable = TypeVar("Comparable", bound="SupportsComparison")
# 实际常用:绑定到基类
class BaseModel:
pass
ModelT = TypeVar("ModelT", bound=BaseModel)
def create_and_validate(cls: type[ModelT], data: dict) -> ModelT:
"""工厂函数,返回类型和传入的类一致"""
instance = cls(**data)
return instance三、Protocol:结构子类型(Python 的接口)
Python 没有 Java 的 interface,但 Protocol 提供了同样的功能,而且更灵活——不需要显式 implements,只需要对象有对应的方法即可(Duck Typing + 静态检查)。
3.1 定义 Protocol
from typing import Protocol, runtime_checkable
@runtime_checkable # 允许用 isinstance 检查
class Serializable(Protocol):
"""可序列化的协议:必须有 to_dict 方法"""
def to_dict(self) -> dict:
...
class Persistable(Protocol):
"""可持久化的协议"""
async def save(self) -> bool:
...
async def delete(self) -> bool:
...
# 实现 Serializable(不需要显式声明)
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def to_dict(self) -> dict:
return {"name": self.name, "age": self.age}
class Article:
def __init__(self, title: str):
self.title = title
def to_dict(self) -> dict:
return {"title": self.title}
def serialize_batch(items: list[Serializable]) -> list[dict]:
"""接受任何实现了 Serializable 协议的对象"""
return [item.to_dict() for item in items]
# User 和 Article 都没有继承 Serializable,但都满足协议
result = serialize_batch([User("老张", 35), Article("Python 指南")])
print(result)3.2 Protocol vs ABC(抽象基类)
| 特性 | Protocol | ABC |
|---|---|---|
| 是否需要显式继承 | 不需要 | 需要 |
| 检查方式 | 结构检查(有没有这个方法) | 注册或继承 |
| 运行时 isinstance | 需要 @runtime_checkable | 原生支持 |
| 跨库使用 | 更灵活 | 需要依赖同一基类 |
四、Generic 类:泛型类
from typing import Generic, TypeVar
T = TypeVar("T")
E = TypeVar("E", bound=Exception)
class Result(Generic[T, E]):
"""
Rust-style Result 类型:表示成功值或错误
Java 工程师可以理解为 Either<T, E>
"""
def __init__(self, value: T | None = None, error: E | None = None):
self._value = value
self._error = error
@classmethod
def ok(cls, value: T) -> "Result[T, E]":
return cls(value=value)
@classmethod
def err(cls, error: E) -> "Result[T, E]":
return cls(error=error)
@property
def is_ok(self) -> bool:
return self._error is None
def unwrap(self) -> T:
if self._error is not None:
raise self._error
return self._value # type: ignore
def unwrap_or(self, default: T) -> T:
return self._value if self.is_ok else default
class Stack(Generic[T]):
"""类型安全的栈"""
def __init__(self):
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 | None:
return self._items[-1] if self._items else None
def __len__(self) -> int:
return len(self._items)五、完整可运行示例
#!/usr/bin/env python3
"""
Python 类型注解完整示例:
TypeVar、Protocol、Generic 在工程中的实际应用
"""
from __future__ import annotations
from abc import ABC
from typing import Any, Callable, Generic, Iterator, Protocol, TypeVar, runtime_checkable
# ===== TypeVar =====
T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")
Num = TypeVar("Num", int, float)
# ===== Protocol 定义 =====
@runtime_checkable
class Comparable(Protocol):
def __lt__(self, other: Any) -> bool: ...
def __le__(self, other: Any) -> bool: ...
@runtime_checkable
class Transformable(Protocol[T]):
def transform(self) -> T: ...
# ===== 泛型函数 =====
def find_max(items: list[Comparable]) -> Comparable | None:
"""找出列表中的最大值(任何可比较类型)"""
if not items:
return None
result = items[0]
for item in items[1:]:
if result < item:
result = item
return result
def pipe(value: T, *transforms: Callable[[T], T]) -> T:
"""管道:依次应用多个变换函数"""
result = value
for transform in transforms:
result = transform(result)
return result
def group_by(items: list[T], key: Callable[[T], K]) -> dict[K, list[T]]:
"""按 key 函数对列表分组"""
result: dict[K, list[T]] = {}
for item in items:
k = key(item)
result.setdefault(k, []).append(item)
return result
# ===== 泛型类 =====
class TypedCache(Generic[K, V]):
"""类型安全的缓存"""
def __init__(self, max_size: int = 100):
self._data: dict[K, V] = {}
self._max_size = max_size
def get(self, key: K) -> V | None:
return self._data.get(key)
def set(self, key: K, value: V) -> None:
if len(self._data) >= self._max_size:
oldest_key = next(iter(self._data))
del self._data[oldest_key]
self._data[key] = value
def __contains__(self, key: K) -> bool:
return key in self._data
def __len__(self) -> int:
return len(self._data)
class PaginatedResult(Generic[T]):
"""分页结果容器"""
def __init__(self, items: list[T], total: int, page: int, page_size: int):
self.items = items
self.total = total
self.page = page
self.page_size = page_size
@property
def total_pages(self) -> int:
return (self.total + self.page_size - 1) // self.page_size
@property
def has_next(self) -> bool:
return self.page < self.total_pages
def __iter__(self) -> Iterator[T]:
return iter(self.items)
def __len__(self) -> int:
return len(self.items)
# ===== 演示 =====
def main():
print("=== TypeVar 演示 ===")
# find_max 接受任何可比较类型
nums = [3, 1, 4, 1, 5, 9, 2, 6]
strs = ["banana", "apple", "cherry"]
print(f"最大数字: {find_max(nums)}") # 9
print(f"最大字符串: {find_max(strs)}") # cherry
# pipe:函数管道
result = pipe(
" Hello World ",
str.strip,
str.lower,
lambda s: s.replace(" ", "_"),
)
print(f"管道处理: {result!r}") # 'hello_world'
# group_by
words = ["apple", "banana", "cherry", "avocado", "blueberry", "cherry"]
grouped = group_by(words, lambda w: w[0])
print(f"按首字母分组: {grouped}")
print("\n=== Generic 类演示 ===")
# TypedCache
user_cache: TypedCache[int, str] = TypedCache(max_size=5)
user_cache.set(1, "老张")
user_cache.set(2, "小陈")
print(f"缓存大小: {len(user_cache)}")
print(f"查询 key=1: {user_cache.get(1)}")
print(f"查询 key=99: {user_cache.get(99)}")
# PaginatedResult
all_items = list(range(1, 101)) # 100 个元素
page_items = all_items[20:40] # 第 2 页(每页 20)
paged: PaginatedResult[int] = PaginatedResult(
items=page_items, total=100, page=2, page_size=20
)
print(f"\n分页结果: 第{paged.page}页,共{paged.total_pages}页")
print(f"当前页元素数: {len(paged)}")
print(f"有下一页: {paged.has_next}")
print("\n=== Protocol 演示 ===")
class Temperature:
def __init__(self, celsius: float):
self.celsius = celsius
def __lt__(self, other: Any) -> bool:
return self.celsius < other.celsius
def __le__(self, other: Any) -> bool:
return self.celsius <= other.celsius
def __repr__(self) -> str:
return f"{self.celsius}°C"
temps = [Temperature(20), Temperature(35), Temperature(15), Temperature(28)]
print(f"最高温度: {find_max(temps)}")
print(f"Temperature 实现了 Comparable: {isinstance(temps[0], Comparable)}")
if __name__ == "__main__":
main()六、踩坑实录 1:TypeVar 不等于多态
# 错误理解:TypeVar 意味着可以传不同类型
T = TypeVar("T")
def bad_func(a: T, b: T) -> T:
return a + b # 如果 T 是 str 和 int 混用会报错
# 正确理解:TypeVar 约束"同一次调用中的类型一致"
result1 = bad_func(1, 2) # T = int,OK
result2 = bad_func("a", "b") # T = str,OK
result3 = bad_func(1, "b") # T 无法同时是 int 和 str,类型检查器报错!七、踩坑实录 2:Protocol 方法签名必须完全匹配
class Serializable(Protocol):
def to_dict(self) -> dict: ...
class BrokenUser:
def to_dict(self, include_private: bool = False) -> dict: # 签名不同!
return {}
# mypy 会报告 BrokenUser 不满足 Serializable
# 即使参数有默认值,严格类型检查也会失败八、踩坑实录 3:忘记 from future import annotations 导致前向引用报错
# 错误:类定义里引用自身,在 Python < 3.10 会报 NameError
class Node:
def __init__(self, next: Node | None = None): # NameError: 'Node' 未定义
self.next = next
# 正确方案1:加引号(字符串引用)
class Node:
def __init__(self, next: "Node | None" = None):
self.next = next
# 正确方案2:在文件开头加 from __future__ import annotations(推荐)
from __future__ import annotations
class Node:
def __init__(self, next: Node | None = None): # 正常!
self.next = next总结
Python 类型系统的进阶工具:
- TypeVar:泛型函数,约束参数和返回值的类型关系
- Protocol:结构子类型,替代 Java 的 interface,无需继承
- Generic[T]:泛型类,让容器类型安全
- Annotated:附加元信息到类型,配合 Pydantic、FastAPI 使用
配合 mypy 或 pyright 做静态类型检查,Python 代码可以达到接近 Java 的类型安全级别。
