Python 高级类型提示实战:Protocol、Generic 与 TypeVar 的深度用法

3949 字
20 分钟
Python 高级类型提示实战:Protocol、Generic 与 TypeVar 的深度用法

你在 Python 里写过 def process(data: dict) -> list: 吗?如果你写过,那么恭喜你——你已经踏入了类型提示的大门。但如果你的项目还在用 Any 满天飞,或者对 ProtocolTypeVarParamSpec 这些高级特性闻所未闻,那么这篇文章就是为你准备的。

Python 的类型系统在过去五年里经历了爆炸式进化:从 3.5 的 typing 模块初版,到 3.10 的联合类型语法 X | Y,到 3.11 的 SelfTypeVarTuple,再到 3.12 的原生泛型语法 class Stack[T]:。今天的 Python 类型系统已经强大到可以写出Any、编译期全检查、运行时零开销的生产级代码。

这篇文章不讲 strintlist[str] 这些基础内容。我们直接深入高级类型系统:Protocol 结构子类型、TypeVar 约束与协变逆变、泛型类的实现模式、ParamSpec 装饰器类型推导、TypeVarTuple 可变泛型。每一步都有完整可运行的代码。

一、Protocol:从名义类型到结构子类型#

1.1 为什么需要 Protocol?#

Python 的类型系统默认是名义子类型(Nominal Subtyping)——只有明确继承自某个类的对象,才被视为该类型的实例:

from abc import ABC, abstractmethod
class Drawable(ABC):
@abstractmethod
def draw(self) -> None: ...
class Circle(Drawable):
def draw(self) -> None:
print("画圆")
class Square(Drawable):
def draw(self) -> None:
print("画方块")
# ❌ 这个类没有继承 Drawable,但它有 draw() 方法
class Triangle:
def draw(self) -> None:
print("画三角形")
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # ✅ 可以
render(Square()) # ✅ 可以
render(Triangle()) # ❌ mypy 报错:Triangle 不是 Drawable 的子类

看到了吗?Triangledraw() 方法,行为完全兼容 Drawable,但因为没继承,就被类型系统拒绝了。这就是名义类型的局限——它认关系不认能力

在 Python 这种鸭子类型的语言里,我们真正需要的是结构子类型(Structural Subtyping):只要一个对象有某个方法,它就是这个类型。

这就是 Protocol 存在的意义。

1.2 Protocol 基础用法#

from typing import Protocol, runtime_checkable
class HasDraw(Protocol):
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print("画圆")
class Square:
def draw(self) -> None:
print("画方块")
# Triangle 不需要继承任何东西
class Triangle:
def draw(self) -> None:
print("画三角形")
def render(shape: HasDraw) -> None:
shape.draw()
render(Circle()) # ✅
render(Square()) # ✅
render(Triangle()) # ✅ mypy 也能通过了!

Protocol 的核心逻辑:不看你继承自谁,只看你有什么方法。只要一个类实现了 Protocol 中定义的所有方法,mypy 就认为它是这个 Protocol 的子类型。

1.3 runtime_checkable:运行时检查#

from typing import Protocol, runtime_checkable
@runtime_checkable
class Serializable(Protocol):
def to_json(self) -> str: ...
@classmethod
def from_json(cls, data: str) -> "Serializable": ...
class User:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
def to_json(self) -> str:
import json
return json.dumps({"name": self.name, "age": self.age})
@classmethod
def from_json(cls, data: str) -> "User":
import json
d = json.loads(data)
return cls(d["name"], d["age"])
user = User("张三", 28)
print(isinstance(user, Serializable)) # True!

⚠️ 注意runtime_checkable 只检查方法是否存在(通过 hasattr),不检查方法签名。运行时检查是弱检查,静态类型检查(mypy)才是强检查。

1.4 Protocol 进阶:泛型 Protocol#

当你需要描述一个「能返回某种类型」的协议时,泛型 Protocol 就派上用场了:

from typing import Protocol, TypeVar, Generic
T = TypeVar("T")
class Repository(Protocol[T]):
def get(self, id: int) -> T | None: ...
def save(self, entity: T) -> None: ...
def delete(self, id: int) -> bool: ...
# 实现:用户仓库
class User:
def __init__(self, id: int, name: str) -> None:
self.id = id
self.name = str
class UserRepository:
def __init__(self) -> None:
self._store: dict[int, User] = {}
def get(self, id: int) -> User | None:
return self._store.get(id)
def save(self, entity: User) -> None:
self._store[entity.id] = entity
def delete(self, id: int) -> bool:
return self._store.pop(id, None) is not None
def process_repo(repo: Repository[User]) -> None:
user = repo.get(1)
if user:
repo.save(user)
repo = UserRepository()
process_repo(repo) # ✅ mypy 自动验证类型匹配

1.5 实战案例:插件系统的类型安全#

from typing import Protocol, TypeVar, Generic
T_in = TypeVar("T_in")
T_out = TypeVar("T_out")
class Transformer(Protocol[T_in, T_out]):
"""数据转换器协议:接收 T_in,产出 T_out"""
def transform(self, data: T_in) -> T_out: ...
class Pipeline(Generic[T_in, T_out]):
def __init__(self) -> None:
self._steps: list[Transformer] = []
def add(self, step: Transformer) -> None:
self._steps.append(step)
def execute(self, data: T_in) -> T_out:
result: object = data
for step in self._steps:
result = step.transform(result)
return result # type: ignore[return-value]
# 具体转换器
class StringToUpper:
def transform(self, data: str) -> str:
return data.upper()
class StringLength:
def transform(self, data: str) -> int:
return len(data)
pipeline: Pipeline[str, int] = Pipeline()
pipeline.add(StringToUpper())
pipeline.add(StringLength())
result = pipeline.execute("hello world")
print(result) # 11

二、TypeVar 深度解析:约束、协变与逆变#

2.1 TypeVar 的三种模式#

TypeVar 是泛型系统的基石,但绝大多数人只用到了它最基础的形态。它有三种关键模式:

from typing import TypeVar
# 模式 1:无约束 —— T 可以是任何类型
T = TypeVar("T")
# 模式 2:约束 —— T 只能是 str 或 int
T_constrained = TypeVar("T_constrained", str, int)
# 模式 3:有界 —— T 必须是 Animal 或其子类
class Animal: ...
class Dog(Animal): ...
T_bound = TypeVar("T_bound", bound=Animal)

2.2 约束模式的陷阱#

from typing import TypeVar
T = TypeVar("T", str, int)
def first(items: list[T]) -> T:
return items[0]
# ✅ 类型推导正确
reveal_type(first(["hello", "world"])) # str
reveal_type(first([1, 2, 3])) # int
# ❌ mypy 报错:不能混合类型
reveal_type(first(["hello", 42])) # Error

关键理解:约束模式不是”可以是 str 或 int”,而是”一旦确定为 str,整个作用域内都必须是 str”。这保证了泛型在函数内部的类型一致性。

2.3 协变(Covariance)与逆变(Contravariance)#

这是泛型系统最容易让人困惑的部分,但理解了之后你会豁然开朗。

from typing import TypeVar, Generic
class Animal: ...
class Dog(Animal): ...
T_co = TypeVar("T_co", covariant=True) # 协变
T_contra = TypeVar("T_contra", contravariant=True) # 逆变
class Producer(Generic[T_co]):
"""生产者:产出 T_co"""
def produce(self) -> T_co: ...
class Consumer(Generic[T_contra]):
"""消费者:接收 T_contra"""
def consume(self, item: T_contra) -> None: ...
# 协变:Producer[Dog] 是 Producer[Animal] 的子类型
dog_producer: Producer[Dog] = Producer()
animal_producer: Producer[Animal] = dog_producer # ✅ 协变安全
# 逆变:Consumer[Animal] 是 Consumer[Dog] 的子类型
animal_consumer: Consumer[Animal] = Consumer()
dog_consumer: Consumer[Dog] = animal_consumer # ✅ 逆变安全

直观记忆法

  • 协变(covariant):产出东西的容器。能产出狗的,一定能产出动物。→ 产出者随类型缩小而缩小
  • 逆变(contravariant):消费东西的容器。能消费动物的,一定能消费狗。→ 消费者随类型缩小而放大
  • 不变(invariant):既能产出又能消费。类型必须精确匹配。

2.4 实战:事件系统的类型安全#

from typing import Protocol, TypeVar, Generic
T_co = TypeVar("T_co", covariant=True)
class Event(Protocol[T_co]):
"""事件协议:携带 T_co 类型的数据"""
@property
def data(self) -> T_co: ...
@property
def timestamp(self) -> float: ...
T_contra = TypeVar("T_contra", contravariant=True)
class EventHandler(Protocol[T_contra]):
"""事件处理器:能处理 T_contra 类型的事件"""
def handle(self, event: T_contra) -> None: ...
class EventBus:
def __init__(self) -> None:
self._handlers: dict[type, list[EventHandler]] = {}
def register(self, event_type: type, handler: EventHandler) -> None:
self._handlers.setdefault(event_type, []).append(handler)
def dispatch(self, event: Event) -> None:
handlers = self._handlers.get(type(event), [])
for handler in handlers:
handler.handle(event)

三、Generic 高级用法:泛型类的正确实现模式#

3.1 泛型缓存类#

from typing import Generic, TypeVar, Callable
import time
K = TypeVar("K")
V = TypeVar("V")
class LRUCache(Generic[K, V]):
def __init__(self, capacity: int, ttl: float = 300.0) -> None:
self._capacity = capacity
self._ttl = ttl
self._store: dict[K, tuple[V, float]] = {}
self._order: list[K] = []
def get(self, key: K) -> V | None:
if key not in self._store:
return None
value, timestamp = self._store[key]
if time.time() - timestamp > self._ttl:
del self._store[key]
self._order.remove(key)
return None
# 移到末尾(最近使用)
self._order.remove(key)
self._order.append(key)
return value
def put(self, key: K, value: V) -> None:
if key in self._store:
self._order.remove(key)
elif len(self._store) >= self._capacity:
# 淘汰最旧的
oldest = self._order.pop(0)
del self._store[oldest]
self._store[key] = (value, time.time())
self._order.append(key)
def __len__(self) -> int:
return len(self._store)
# 使用:类型完全自动推导
cache: LRUCache[str, int] = LRUCache(capacity=3, ttl=60.0)
cache.put("a", 1)
cache.put("b", 2)
result = cache.get("a") # result 类型推导为 int | None

3.2 泛型结果类型(Result Pattern)#

from typing import Generic, TypeVar, Callable
T = TypeVar("T")
E = TypeVar("E")
class Ok(Generic[T]):
def __init__(self, value: T) -> None:
self._value = value
@property
def value(self) -> T:
return self._value
@property
def is_ok(self) -> bool:
return True
@property
def is_err(self) -> bool:
return False
def map(self, fn: Callable[[T], T]) -> "Ok[T]":
return Ok(fn(self._value))
def map_err(self, fn: Callable[[E], E]) -> "Ok[T]":
return self # Ok 状态下不转换错误
def unwrap(self) -> T:
return self._value
class Err(Generic[E]):
def __init__(self, error: E) -> None:
self._error = error
@property
def error(self) -> E:
return self._error
@property
def is_ok(self) -> bool:
return False
@property
def is_err(self) -> bool:
return True
def map(self, fn: Callable) -> "Err[E]":
return self # Err 状态下不转换值
def map_err(self, fn: Callable[[E], E]) -> "Err[E]":
return Err(fn(self._error))
def unwrap(self) -> None:
raise RuntimeError(f"Called unwrap() on Err: {self._error}")
Result = Ok[T] | Err[E]
# 使用示例
def divide(a: float, b: float) -> Result[float, str]:
if b == 0:
return Err("除数不能为零")
return Ok(a / b)
result = divide(10, 3)
if result.is_ok:
print(f"结果: {result.unwrap()}") # 结果: 3.3333333333333335
else:
print(f"错误: {result.error}")
result2 = divide(10, 0)
if result2.is_ok:
print(f"结果: {result2.unwrap()}")
else:
print(f"错误: {result2.error}") # 错误: 除数不能为零

3.3 Python 3.12+ 原生泛型语法#

Python 3.12 引入了原生泛型语法,告别 TypeVar 的样板代码:

# Python 3.12+(不需要 from typing import TypeVar, Generic)
class Stack[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()
class Result[T, E]:
def __init__(self, ok: bool, value: T | None = None, error: E | None = None) -> None:
self.ok = ok
self.value = value
self.error = error
# 泛型函数也可以直接写
def first[T](items: list[T]) -> T:
if not items:
raise IndexError("List is empty")
return items[0]

四、ParamSpec:装饰器的类型推导救星#

4.1 问题:装饰器丢掉了参数类型信息#

from typing import Callable, TypeVar
T = TypeVar("T")
# ❌ 传统方式:装饰器会丢失被装饰函数的参数类型
def logged(func: Callable[..., T]) -> Callable[..., T]:
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logged
def add(a: int, b: int) -> int:
return a + b
# mypy 无法推导 add 的参数类型
# add 被推导为 Callable[..., int],丢失了 (int, int) 的信息

4.2 ParamSpec 解决方案#

from typing import Callable, TypeVar, ParamSpec
P = ParamSpec("P")
T = TypeVar("T")
def logged(func: Callable[P, T]) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logged
def add(a: int, b: int) -> int:
return a + b
# ✅ mypy 正确推导:add 仍然是 (int, int) -> int
result = add(1, 2) # 类型推导为 int
# add("hello", 2) # ❌ mypy 会报错!

4.3 带参数的装饰器#

from typing import Callable, ParamSpec, TypeVar
import time
P = ParamSpec("P")
T = TypeVar("T")
def retry(max_attempts: int = 3, delay: float = 1.0) -> Callable[[Callable[P, T]], Callable[P, T]]:
def decorator(func: Callable[P, T]) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
last_exception: Exception | None = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"Attempt {attempt + 1}/{max_attempts} failed: {e}")
if attempt < max_attempts - 1:
time.sleep(delay)
raise last_exception # type: ignore[misc]
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unstable_api(x: int) -> str:
import random
if random.random() < 0.7:
raise ConnectionError("网络不稳定")
return f"success: {x}"
result = unstable_api(42) # 类型推导为 str

五、TypeVarTuple:可变数量泛型参数#

5.1 什么是 TypeVarTuple?#

TypeVarTuple(Python 3.11+)允许你表达「可变数量的类型参数」,类似于函数中的 *args,但是针对类型系统的:

from typing import Generic, TypeVar, TypeVarTuple
Ts = TypeVarTuple("Ts")
T = TypeVar("T")
class Tensor(Generic[*Ts]):
"""N 维张量:每个维度可以有不同类型"""
def __init__(self, data: object, shape: tuple[*Ts]) -> None:
self.data = data
self.shape = shape
# 2D 张量
tensor_2d: Tensor[int, str] = Tensor([[1, 2], [3, 4]], shape=(2, 2))
# 3D 张量
tensor_3d: Tensor[int, str, float] = Tensor(
[[[1, 2], [3, 4]], [[5, 6], [7, 8]]],
shape=(2, 2, 2)
)

5.2 实战:类型安全的函数管道#

from typing import Generic, TypeVar, TypeVarTuple, Callable, cast
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
class Pipe(Generic[T, *Ts]):
"""类型安全的函数管道"""
def __init__(self, initial: T) -> None:
self._value: object = initial
def pipe(self, fn: Callable[[T], T]) -> "Pipe[T, *Ts]":
self._value = fn(self._value) # type: ignore[assignment]
return self
def result(self) -> T:
return cast(T, self._value)
def add_one(x: int) -> int:
return x + 1
def double(x: int) -> int:
return x * 2
def to_str(x: int) -> str:
return str(x)
# 使用管道
result = (
Pipe(10)
.pipe(add_one) # 11
.pipe(double) # 22
.pipe(to_str) # "22"
.result()
)
print(result) # "22"

六、Self:方法返回自身类型的优雅写法#

6.1 问题:链式调用的类型丢失#

from typing import TypeVar
T = TypeVar("T", bound="Builder")
class Builder:
def __init__(self) -> None:
self._parts: list[str] = []
def add_part(self, part: str) -> T: # ❌ 子类的类型会丢失
self._parts.append(part)
return self # type: ignore[return-value]
class AdvancedBuilder(Builder):
def add_special(self) -> "AdvancedBuilder":
self._parts.append("special")
return self
# 问题:
# Builder().add_part("a").add_part("b") # 返回类型是 Builder
# AdvancedBuilder().add_part("a") # 返回类型是 Builder,不是 AdvancedBuilder!

6.2 Self 解决方案#

from typing import Self
class Builder:
def __init__(self) -> None:
self._parts: list[str] = []
def add_part(self, part: str) -> Self:
self._parts.append(part)
return self
def build(self) -> str:
return " + ".join(self._parts)
class AdvancedBuilder(Builder):
def add_special(self) -> Self:
self._parts.append("special")
return self
# ✅ 现在类型正确推导
ab = AdvancedBuilder()
result = ab.add_part("a").add_special().add_part("b").build()
print(result) # "a + special + b"

Self 适用于所有返回 self 的方法:工厂方法、构建器模式、链式 API。它让子类的返回类型自动正确,不需要手动重写。

七、类型守卫:TypeGuard 与 TypeIs#

7.1 TypeGuard:自定义类型收窄#

from typing import TypeGuard
def is_string_list(value: list[object]) -> TypeGuard[list[str]]:
return all(isinstance(item, str) for item in value)
def process(items: list[object]) -> None:
if is_string_list(items):
# ✅ mypy 知道 items 现在是 list[str]
for item in items:
print(item.upper()) # 类型安全!
else:
print("不是字符串列表")

7.2 TypeIs:更精确的守卫(Python 3.13+)#

from typing import TypeIs
def is_positive_int(value: int | float) -> TypeIs[int]:
return isinstance(value, int) and value > 0
def process_number(n: int | float) -> None:
if is_positive_int(n):
# ✅ mypy 知道 n 现在是 int
print(n.bit_count()) # int 专有方法
else:
print(n)

TypeGuard vs TypeIs 的区别

  • TypeGuard:收窄类型,但不排除原类型中的其他可能性
  • TypeIs:收窄类型,在 else 分支中自动排除收窄后的类型

八、实战演练:构建类型安全的 ORM 查询接口#

让我们把所有高级类型特性组合起来,构建一个类型安全的 ORM 查询接口:

from typing import (
Protocol, TypeVar, Generic, ParamSpec, Self,
Callable, runtime_checkable
)
from dataclasses import dataclass
# ===== 模型定义 =====
@dataclass
class User:
id: int
name: str
email: str
@dataclass
class Post:
id: int
title: str
author_id: int
# ===== 查询构建器 =====
T = TypeVar("T")
P = ParamSpec("P")
class WhereClause(Protocol[T]):
"""WHERE 条件协议"""
def evaluate(self, entity: T) -> bool: ...
class QueryBuilder(Generic[T]):
def __init__(self, model: type[T]) -> None:
self._model = model
self._where_clauses: list[WhereClause[T]] = []
self._limit: int | None = None
self._order_by: str | None = None
def where(self, clause: WhereClause[T]) -> Self:
self._where_clauses.append(clause)
return self
def limit(self, n: int) -> Self:
self._limit = n
return self
def order_by(self, field: str) -> Self:
self._order_by = field
return self
def execute(self, db: list[T]) -> list[T]:
results = db
for clause in self._where_clauses:
results = [r for r in results if clause.evaluate(r)]
if self._order_by:
results.sort(key=lambda r: getattr(r, self._order_by))
if self._limit:
results = results[:self._limit]
return results
# ===== 条件构造 =====
class FieldEquals(Generic[T]):
def __init__(self, field: str, value: object) -> None:
self._field = field
self._value = value
def evaluate(self, entity: T) -> bool:
return getattr(entity, self._field) == self._value
# ===== 使用示例 =====
db_users: list[User] = [
User(1, "张三", "zhangsan@example.com"),
User(2, "李四", "lisi@example.com"),
User(3, "王五", "wangwu@example.com"),
]
# 类型安全的查询
query: QueryBuilder[User] = QueryBuilder(User)
results = (
query
.where(FieldEquals("name", "李四"))
.limit(1)
.execute(db_users)
)
print(f"找到 {len(results)} 条结果")
for r in results:
print(f" {r.name} - {r.email}")
# 找到 1 条结果
# 李四 - lisi@example.com

九、mypy 配置最佳实践#

类型提示的价值完全取决于你是否正确配置了静态检查工具。以下是生产级 mypy.ini 配置:

[mypy]
# 严格模式
strict = True
# 忽略缺失类型注解的第三方库
ignore_missing_imports = True
# 禁止隐式 Optional
no_implicit_optional = True
# 检查未使用的 type: ignore 注释
warn_unused_ignores = True
# 检查未使用的配置
warn_unused_configs = True
# 报告未注解函数的返回类型
disallow_untyped_defs = True
# 禁止泛型参数中的 Any
disallow_any_generics = True
# 要求子类显式调用 super().__init__()
disallow_subclassing_any = True
# 严格 Optional 检查
strict_optional = True
# 检查返回值
warn_return_any = True
# 忽略生成的代码
exclude = (build|dist|venv|\.venv|__pycache__|\.git)/

运行命令:

Terminal window
mypy src/ --config-file mypy.ini --show-error-codes

十、总结#

特性引入版本核心用途一句话总结
Protocol3.8结构子类型不看你继承谁,只看你有什么方法
TypeVar3.5泛型参数让类和方法对类型开放
ParamSpec3.10装饰器推导保留被装饰函数的参数类型信息
TypeVarTuple3.11可变泛型类型系统的 *args
Self3.11链式调用方法返回自身类型,子类自动正确
TypeGuard3.10类型守卫自定义类型收窄函数
TypeIs3.13精确守卫收窄且排除的 TypeGuard 升级版
原生泛型 class Foo[T]3.12语法简化告别 TypeVar 样板代码

Python 的类型系统已经从装饰性注解进化为工程级基础设施。用好这些高级特性,你可以:

  1. 零运行时开销:类型信息在编译期擦除,不影响性能
  2. 编译期发现 bug:90% 的类型错误在运行前被拦截
  3. IDE 智能补全:类型就是文档,IDE 自动推导
  4. 重构安全感:改一个方法签名,mypy 告诉你哪里需要改
  5. API 契约明确:Protocol 定义接口,实现自动验证

不要停留在 strint 的舒适区。ProtocolTypeVarParamSpec 用到你的项目中,你会体验到一种全新的 Python 开发方式——既有动态语言的灵活性,又有静态语言的安全感。

延伸阅读#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Python 高级类型提示实战:Protocol、Generic 与 TypeVar 的深度用法
https://boke.hackerdream.xyz/posts/python-typing-advanced/
作者
晴天
发布于
2026-05-29
许可协议
CC BY-NC-SA 4.0
随机文章 随机推荐
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
155
分类
24
标签
387
总字数
345,424
运行时长
0
最后活动
0 天前

目录