You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为字典子类特定键添加类型提示(禁用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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 17:33:07