为什么参数类型“Dict[str, Union[str, int]]”不接受类型“Dict[str, str]”的值(mypy)

Hub*_*bro 7 python typing mypy

我有一个传递给模板的变量字典的类型:

VariablesDict = Dict[str, Union[int, float, str, None]]
Run Code Online (Sandbox Code Playgroud)

基本上,任何字典,其中键是字符串,值是字符串、数字或 None。我在几个与模板相关的函数中使用这种类型。

以这个示例函数为例:

def render_template(name: str, variables: VariablesDict):
    ...
Run Code Online (Sandbox Code Playgroud)

使用字典文字调用此函数效果很好:

render_template("foo", {"key": "value"})
Run Code Online (Sandbox Code Playgroud)

但是,如果我首先将字典分配给变量,如下所示:

variables = {"key": "value"}

render_template("foo", variables)
Run Code Online (Sandbox Code Playgroud)

Mypy 报错:

“render_template”的参数 2 具有不兼容的类型“Dict[str, str]”;预期“Dict [str,Union [int,float,str,None]]”

在我看来,任何类型的值都Dict[str, str]应该安全地传递给需要类型参数的函数Dict[str, Union[int, float, str, None]]。为什么默认情况下不起作用?我能做些什么来让这项工作成功吗?

Sam*_*ord 8

它不起作用的原因是它Dict是可变的,因此接受 a 的函数Dict[str, int|float|str|None]可以合理地将任何这些类型插入到其参数中。如果参数实际上是 a Dict[str, str],那么它现在包含违反其类型的值。(有关这方面的更多信息,请谷歌“协方差/逆变/不变性”和“里氏替换原理”——作为一般规则,可变容器对其泛型类型是不变的。)

只要render_template不需要修改传递给它的字典,一个简单的解决方法就是让它采用 a Mapping(这是一个抽象超类型,dict并不意味着可变性,因此是协变的)而不是 a Dict

def render_template(name: str, variables: Mapping[str, Union[int, float, str, None]]):
    ...
Run Code Online (Sandbox Code Playgroud)

  • “作为一般规则,可变容器对其泛型类型是逆变的”——可变容器对其泛型类型是*不变*的!总的规则是:进去了就可以逆变。如果出来了,它可以是协变的。如果它既进又出(就像可变容器的元素一样),它只能是不变的。 (2认同)