合并字典赋值的类型检查不兼容问题:单个字典符合要求但合并后报错
合并字典赋值的类型检查不兼容问题:单个字典符合要求但合并后报错
我最近碰到一个挺头疼的类型检查小坑,相信不少用Python类型注解的朋友可能也遇到过:明明两个单独的字典都完全符合指定的类型要求,可一旦用|运算符把它们合并,再赋值给对应类型的变量或者作为返回值时,MyPy和Pyright都会抛出类型不兼容的错误。
先给大家看一下我遇到问题的具体代码:
from typing import Literal type MyType = Literal['a', 'b'] def f() -> dict[MyType, str]: # 单个字典赋值完全没问题,类型检查直接通过 d1: dict[MyType, str] = {'a': 'foo'} # 哪怕是字面量字典,单独标注类型也能顺利通过检查 d2: dict[MyType, str] = {'b': 'bar'} # 但这里用|合并后返回,类型检查器直接报错 return d1 | d2
为啥会出现这种离谱的情况呢?我琢磨了下类型检查器的推断逻辑,发现问题出在字典合并运算符|的类型推断上:当合并一个显式标注了窄类型(比如dict[MyType, str])的字典和另一个字典时,类型检查器有时候会把合并后的结果推断成更宽泛的类型(比如dict[str, str]),哪怕两个字典的键都完全符合MyType的要求。如果其中一个字典是未显式标注类型的字面量,这个问题会更明显——类型检查器会直接把字面量字典推断成dict[str, str],合并后的结果自然就不符合dict[MyType, str]的要求了。
那怎么解决这个问题呢?我整理了几个实用的办法:
- 显式标注合并结果的类型:用
typing.cast强制告诉类型检查器合并后的字典类型,不过这个办法要谨慎用,得确保你合并的两个字典键确实都属于MyType,不然会埋下类型隐患:from typing import Literal, cast type MyType = Literal['a', 'b'] def f() -> dict[MyType, str]: d1: dict[MyType, str] = {'a': 'foo'} d2: dict[MyType, str] = {'b': 'bar'} return cast(dict[MyType, str], d1 | d2) - 用字典推导式替代
|运算符:字典推导式的类型推断逻辑更精准,类型检查器能正确识别合并后的键类型:from typing import Literal type MyType = Literal['a', 'b'] def f() -> dict[MyType, str]: d1: dict[MyType, str] = {'a': 'foo'} d2: dict[MyType, str] = {'b': 'bar'} return {k: v for d in (d1, d2) for k, v in d.items()} - 升级类型检查器+显式标注所有字典类型:如果一定要用
|运算符,先确保每个参与合并的字典都显式标注为dict[MyType, str],同时升级到较新版本的MyPy或Pyright(比如MyPy 1.5+、Pyright 1.1.320+),新版本的类型检查器已经优化了字典合并的类型推断逻辑,能正确识别合并后的类型。
其实本质上这是类型检查器对字典合并运算符的类型推断不够完善导致的小问题,只要针对性地给类型检查器足够的提示,就能轻松搞定啦。
内容来源于stack exchange
相关产品推荐
相关产品推荐

