Python 字典的静态类型分析

Static type analysis with Python dicts

有人可以解释为什么这段代码虽然有效,但会导致 mypy 静态分析器以多种方式报错:

ranges = dict()
ranges['max'] = 0
ranges['services'] = []
ranges['services'].append('a')

即:

error: Incompatible types in assignment (expression has type "List[<nothing>]", target has type "int")

error: "int" has no attribute "append"

如果我只是将类型提示添加到 ranges: dict = dict() 的初始变量,它就可以正常工作。

我很困惑为什么静态分析器不能自己解决这个问题,尤其是当我在第一个实例中使用 dict 关键字来初始化字典时。

字典通常用作键控集合,最重要的操作是查找与任意键关联的值。通常在字典中,每个键都有相同的类型,每个值也有相同的类型;如果值是异构的,则表达式 ranges[key] 不一定具有特定类型(尽管您可以将其表示为联合)。

在您的代码中,静态分析器正在尝试推断您的字典的类型。它期望的类型是 Dict[K, V] 形式,其中 KV 尚未确定。第一个赋值 ranges['max'] = 0 给出了关于两个未知数的信息:K 似乎是 strV 似乎是 int。所以此时,ranges被推断为类型Dict[str, int].

接下来的两行会给出错误,因为空列表不能用作 Dict[str, int] 中的值,而 Dict[str, int] 中的值没有 [=23] =]方法。

显式类型注释 ranges: dict = dict() 通过指定这是一个异构字典来否决默认行为,因此值不必都具有相同的类型。鉴于该信息,静态分析器不会假设因为其中一个值是 int 它们都必须是 ints.

由于您没有提供任何注释,mypy 必须尝试推断类型。它可以做出一些选择:

  • ranges = dict() 表示 range: Dict[Any, Any]

    这将接受您的所有代码。它还会接受 ranges['services'].keys() 或任何其他操作,因为它会删除所有类型信息。

  • ranges['max'] = 0 表示 range: Dict[str, int]

    这就是 mypy 的选择。它拒绝任何非整数的值,特别是您的 list.

  • ranges['services'] = [] 表示 range: Dict[str, list]

    这只接受您的 ranges['services'] 字段。 ranges['max'] 无效,因为它不是 list.

  • ranges['max'] = 0 ranges['services'] = [] 意味着 range: Dict[str, Union[int, list]]

    这接受值的分配。它拒绝 ranges['services'].append('a') 因为 ranges['services'] 可能 int.

基本上,none 这些工作。使用普通 Dict[K, V],您的代码无法正确输入。


您可以使用 typing.TypedDict 显式注释 ranges。由于dict一般不限于静态键值对,所以mypy不会自己推断。

class Ranges(TypedDict):
    max: int
    services: List[str]

ranges: Ranges = dict()