如何强制转换TypedDict避免Mypy赋值类型不兼容报错
问题场景
在对执行效率要求极高的函数中实现如下简化逻辑,要求全程避免对象拷贝,且因后续业务规则限制,不能通过NotRequired调整TypedDict定义:
from typing import TypedDict, NotRequired, cast Mytd1 = TypedDict("Mytd1", {"a": NotRequired[str], "x": int}) Mytd_extra = TypedDict("Mytd_extra", {"a": str, "x": int, "z": str}) def foo(y: Mytd1) -> Mytd_extra: # 要求入参绝对不能包含z字段 y = cast(Mytd_extra, y) # 此处触发mypy报错 y["z"] = "hooray" if "a" not in y: y["a"] = "whatever" return y
Mypy会对上述cast赋值操作抛出incompatible types in assignment(赋值类型不兼容)报错。问题根源为a字段的类型差异:a在返回类型Mytd_extra中是必填字段,但入参类型Mytd1中a是可选字段,mypy不会跟踪后续字段补全逻辑,会直接判定赋值时类型不兼容。
排除的实现方案
以下两种方案均无法满足需求:
- 方案1:废弃原有
Mytd1定义,将Mytd_extra的z字段设为NotRequired
拒绝原因:该定义会允许调用方向# 不可用实现 Mytd_extra = TypedDict("Mytd_extra", {"x": int, "z": NotRequired[str]})foo传入{"x": 5, "z": "asdf"}这类携带z字段的对象,违反入参规则——调用方仅允许传入不含z字段的入参,例如{"x": 5}。 - 方案2:拷贝原字典生成新对象后修改返回
拒绝原因:该函数调用频次极高,字典拷贝会产生不必要的性能开销,无法满足效率要求。# 不可用实现 def foo(y: Mytd1) -> Mytd_extra: new_var: Mytd_extra = {k: v for k,v in y.items()} new_var["z"] = "ohno" return new_var
零开销正确实现
cast是仅作用于类型检查层的语法,运行时不会执行任何逻辑,也不会触发对象拷贝。只需调整类型标注的位置,避开mypy对未完成字段补全对象的类型校验即可,实现代码如下:
from typing import TypedDict, NotRequired, cast Mytd1 = TypedDict("Mytd1", {"a": NotRequired[str], "x": int}) Mytd_extra = TypedDict("Mytd_extra", {"a": str, "x": int, "z": str}) def foo(y: Mytd1) -> Mytd_extra: # 原地添加z字段,仅忽略本行「初始类型不存在z键」的类型报错 y["z"] = "hooray" # type: ignore[typeddict-unknown-key] # 补全可能缺失的必填a字段 if "a" not in y: y["a"] = "whatever" # 此时所有必填字段均已填充,cast为目标返回类型,无运行时开销 return cast(Mytd_extra, y)
方案特性
- 全程原地操作入参字典,无任何拷贝逻辑,性能完全满足高频调用要求
- 入参类型仍保持为
Mytd1,调用方传入带z字段的对象时会被mypy直接拦截,符合入参校验规则 - 类型检查豁免仅作用于单条添加字段的语句,不会扩大豁免范围,不会引入额外类型安全隐患
内容的提问来源于stack exchange,提问作者Tommy
相关产品推荐
相关产品推荐

