为字典子类特定键添加类型提示(禁用TypedDict)及mypy报错解决
特定键类型约束的字典类型提示问题解析
我需要为字典的特定键添加类型提示:仅键"intkey"对应int类型值,其余键对应str类型值。注意不能使用TypedDict,因为无法提前知晓所有键。
尝试的代码如下,但无法通过mypy 1.4的检查:
from typing import Dict, Union, Literal, overload class SpecialDict(Dict[str, Union[int, str]]): @overload # <---- 错误A def __getitem__(self, key: Literal["intkey"], /) -> int: ... @overload def __getitem__(self, key: str, /) -> str: ... def __getitem__(self, key, /): return super().__getitem__(key) @overload # <---- 错误B def __setitem__(self, key: Literal["intkey"], val: int, /) -> None: ... @overload def __setitem__(self, key: str, val: str, /) -> None: ... def __setitem__(self, key, val, /): return super().__setitem__(key, val)
mypy报错信息:
- 错误A
Overloaded function signatures 1 and 2 overlap with incompatible return types [misc]
- 错误B
Signature of "__setitem__" incompatible with supertype "dict" [override] Superclass: def __setitem__(self, str, Union[int, str], /) -> None Subclass: @overload def __setitem__(self, Literal['intkey'], int, /) -> None @overload def __setitem__(self, str, str, /) -> None Signature of "__setitem__" incompatible with supertype "MutableMapping" [override] Superclass(mypy note): def __setitem__(self, str, Union[int, str], /) -> None Subclass: @overload def __setitem__(self, Literal['intkey'], int, /) -> None @overload def __setitem__(self, str, str, /) -> None
错误原因分析
错误A原因
两个__getitem__的重载签名存在重叠:Literal["intkey"]是str的子集,当传入键"intkey"时,两个重载都能匹配,但返回类型分别是int和str,mypy无法确定应该使用哪个签名,因此报错。
错误B原因
这是违反**里氏替换原则(LSP)**导致的:
- 父类
Dict[str, Union[int, str]]的__setitem__允许给任意str键赋值Union[int, str]类型的值(包括给"intkey"赋值str、给其他键赋值int)。 - 子类的
__setitem__重载只允许两种合法操作:给"intkey"赋值int、给其他str键赋值str,没有覆盖父类允许的所有情况。
mypy认为子类不能被安全地替换成父类使用,因此报错签名不兼容。
解决方案
方案1:调整重载签名(仍继承Dict,无法完全禁止非法操作)
针对错误A,修改第二个__getitem__的键类型,排除"intkey"避免签名重叠;同时为了兼容父类,需补充覆盖所有父类允许的操作(但会保留非法操作的可能性):
from typing import Dict, Union, Literal, overload from typing_extensions import Not class SpecialDict(Dict[str, Union[int, str]]): @overload def __getitem__(self, key: Literal["intkey"], /) -> int: ... @overload def __getitem__(self, key: Not[Literal["intkey"]], /) -> str: ... def __getitem__(self, key, /): return super().__getitem__(key) @overload def __setitem__(self, key: Literal["intkey"], val: int, /) -> None: ... @overload def __setitem__(self, key: Not[Literal["intkey"]], val: str, /) -> None: ... # 补充父类允许的其他情况 @overload def __setitem__(self, key: Literal["intkey"], val: str, /) -> None: ... @overload def __setitem__(self, key: str, val: int, /) -> None: ... def __setitem__(self, key, val, /): return super().__setitem__(key, val)
方案2:使用Protocol定义自定义类型(推荐,严格约束类型)
不继承Dict,而是用Protocol定义符合需求的字典接口,完全约束键值类型且不违反LSP:
from typing import Protocol, Literal, overload class SpecialDictProtocol(Protocol): @overload def __getitem__(self, key: Literal["intkey"], /) -> int: ... @overload def __getitem__(self, key: str, /) -> str: ... @overload def __setitem__(self, key: Literal["intkey"], val: int, /) -> None: ... @overload def __setitem__(self, key: str, val: str, /) -> None: ... # 使用示例 def process_dict(d: SpecialDictProtocol) -> None: int_val: int = d["intkey"] # 类型检查通过 str_val: str = d["other_key"] # 类型检查通过 d["intkey"] = 123 # 类型检查通过 d["other_key"] = "test" # 类型检查通过 # d["intkey"] = "invalid" # mypy会报错,符合约束 # d["other_key"] = 456 # mypy会报错,符合约束 # 运行时使用普通dict,类型检查用Protocol约束 my_dict: SpecialDictProtocol = {"intkey": 1, "other_key": "abc"}
内容的提问来源于stack exchange,提问作者Andreas H.
相关产品推荐
相关产品推荐

