如何为接受泛型Event容器的函数安全添加类型注解?
解决方案
1. 使用绑定到Event的不变泛型参数
保持Event类的泛型参数为不变(invariant,默认无需显式声明),同时为EventRepository定义泛型协议,使用绑定到Event的TypeVar约束save_events的参数类型。既保证from_data的类型安全,又能让save_events接受任意Event子类的序列。
代码示例
from typing import TypeVar, Protocol, Sequence, Type # 定义绑定到Event的不变TypeVar T = TypeVar("T", bound="Event") class Event: def __init__(self, data: dict): self.data = data @classmethod def from_data(cls: Type[T], data: dict) -> T: """自定义构造器,返回具体的Event子类实例""" return cls(data) # 泛型EventRepository协议 class EventRepository(Protocol[T]): def save_events(self, events: Sequence[T]) -> None: """保存指定类型的Event序列""" ... # 通用内存实现:支持任意Event子类 class InMemoryEventRepository(EventRepository[T]): def save_events(self, events: Sequence[T]) -> None: print(f"Saved {len(events)} events of type {type(events[0]).__name__}") # 具体Event子类 class UserCreatedEvent(Event): @classmethod def from_data(cls: Type["UserCreatedEvent"], data: dict) -> "UserCreatedEvent": if "user_id" not in data: raise ValueError("Missing user_id in data") return cls(data) # 使用示例 if __name__ == "__main__": # 针对UserCreatedEvent的仓库 user_event_repo: EventRepository[UserCreatedEvent] = InMemoryEventRepository() user_events = [ UserCreatedEvent.from_data({"user_id": 1}), UserCreatedEvent.from_data({"user_id": 2}) ] user_event_repo.save_events(user_events) # mypy类型检查通过 # 通用仓库:接受所有Event子类 universal_repo: EventRepository[Event] = InMemoryEventRepository() mixed_events = [ UserCreatedEvent.from_data({"user_id": 3}), Event.from_data({"key": "value"}) ] universal_repo.save_events(mixed_events) # mypy类型检查通过
2. 为from_data的输入数据添加泛型约束(可选)
如果不同Event子类的from_data需要接收不同结构的输入数据,可以给Event添加第二个泛型参数,专门约束输入数据的类型,进一步提升类型安全性。
代码示例
from typing import TypeVar, Protocol, Sequence, Type, Generic # 定义两个不变TypeVar:E绑定Event,D绑定输入数据类型 E = TypeVar("E", bound="Event") D = TypeVar("D") class Event(Generic[D]): def __init__(self, data: D): self.data = data @classmethod def from_data(cls: Type[E], data: D) -> E: return cls(data) # 自定义输入数据类型 class UserCreatedData: def __init__(self, user_id: int, username: str): self.user_id = user_id self.username = username class UserCreatedEvent(Event[UserCreatedData]): @classmethod def from_data(cls: Type["UserCreatedEvent"], data: UserCreatedData) -> "UserCreatedEvent": return cls(data) class EventRepository(Protocol[E]): def save_events(self, events: Sequence[E]) -> None: ... class InMemoryEventRepository(EventRepository[E]): def save_events(self, events: Sequence[E]) -> None: print(f"Saved {len(events)} events") # 使用示例 user_data = UserCreatedData(4, "johndoe") user_event = UserCreatedEvent.from_data(user_data) repo: EventRepository[UserCreatedEvent] = InMemoryEventRepository() repo.save_events([user_event]) # mypy类型检查通过
关键说明
- 避免协变:协变类型变量不能出现在方法的输入参数位置(比如
from_data的data参数),会导致mypy报错,且协变不符合Event既作为输入(构造)又作为输出(保存)的实际场景,保持泛型参数为不变是正确选择。 - 泛型协议的作用:通过泛型
EventRepository[T],可以为特定Event子类创建类型安全的仓库,也可以创建通用仓库接受所有Event子类,兼顾兼容性与类型检查。 - from_data的类型安全:
cls: Type[T]确保构造器返回当前调用类的实例(子类调用时返回子类实例),配合泛型约束,完全避免Any的使用。
内容的提问来源于stack exchange,提问作者takeshi2010
相关产品推荐
相关产品推荐

