Mypy报错:存储泛型可调用对象的字典类型不兼容
问题描述
我在字典中存储Callable对象时遇到了Mypy报错。这些Callable接收BinLogEvent的子类作为参数,并返回BaseEventSchema(Pydantic模型)的子类。我在代码其他类似场景中用过相同绑定的TypeVar且正常运行,但不清楚这里的问题出在哪。
已尝试的方案:
- 使用实际基类、类型变量及其组合
- 用
typing.Dict替代原生dict
环境:Python 3.10、Mypy 0.971
相关代码:
EventSchema = TypeVar("EventSchema", bound=BaseEventSchema) # 所有事件Schema的基类 LogEvent = TypeVar("LogEvent", bound=BinLogEvent) # 所有事件的基类 def _serialize_rows_event(event: RowsEvent) -> RowEventSchema: ... def _serialize_query_event(event: QueryEvent) -> QueryEventSchema: ... # Mypy对两个字典条目都报错: # Dict entry 0 has incompatible type "str": "Callable[[RowsEvent], RowEventSchema]"; # expected "str": "Callable[[LogEvent], EventSchema]" _serializer_mapping: dict[str, Callable[[LogEvent], EventSchema]] = { RowsEvent.__name__: _serialize_rows_event, QueryEvent.__name__: _serialize_query_event, } # 使用该字典的函数没有报错 def serialize_event(event: LogEvent) -> EventSchema: handler = _serializer_mapping[_get_type_str(event)] return handler(event)
解决方案
报错原因
Mypy判定类型不兼容的核心逻辑是:Callable[[LogEvent], EventSchema]要求函数能接收任意LogEvent子类并返回任意EventSchema子类,但你的_serialize_rows_event只能接收RowsEvent(更具体的子类),无法处理其他LogEvent子类,因此Mypy认为类型不匹配。
解决方法
方法1:类型转换快速消除报错
如果能确保serialize_event会严格根据事件类型匹配对应处理器,可以用cast告诉Mypy忽略局部类型检查:
from typing import cast _serializer_mapping: dict[str, Callable[[LogEvent], EventSchema]] = { RowsEvent.__name__: cast(Callable[[LogEvent], EventSchema], _serialize_rows_event), QueryEvent.__name__: cast(Callable[[LogEvent], EventSchema], _serialize_query_event), }
方法2:放宽字典类型并配合返回值转换
将字典类型放宽到基类层面,再在serialize_event中对返回值做类型转换,兼顾类型安全和代码简洁:
from typing import cast, TypeVar LogEventT = TypeVar("LogEventT", bound=BinLogEvent) EventSchemaT = TypeVar("EventSchemaT", bound=BaseEventSchema) _serializer_mapping: dict[str, Callable[[BinLogEvent], BaseEventSchema]] = { RowsEvent.__name__: _serialize_rows_event, QueryEvent.__name__: _serialize_query_event, } def serialize_event(event: LogEventT) -> EventSchemaT: handler = _serializer_mapping[_get_type_str(event)] return cast(EventSchemaT, handler(event))
方法3:改用functools.singledispatch(推荐)
完全放弃字典映射,用Python标准库的singledispatch实现多态处理,Mypy能完美支持这种写法,类型检查更严格,代码扩展性也更好:
from functools import singledispatch from typing import TypeVar LogEventT = TypeVar("LogEventT", bound=BinLogEvent) EventSchemaT = TypeVar("EventSchemaT", bound=BaseEventSchema) @singledispatch def serialize_event(event: BinLogEvent) -> BaseEventSchema: raise NotImplementedError(f"未找到事件类型 {type(event)} 的序列化器") @serialize_event.register def _(event: RowsEvent) -> RowEventSchema: # 原_serialize_rows_event的逻辑 ... @serialize_event.register def _(event: QueryEvent) -> QueryEventSchema: # 原_serialize_query_event的逻辑 ...
新增事件类型时,只需添加对应的register装饰器函数即可,无需维护字典映射。
内容的提问来源于stack exchange,提问作者alenzo-pinc
相关产品推荐
相关产品推荐

