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]
形式,其中 K
和 V
尚未确定。第一个赋值 ranges['max'] = 0
给出了关于两个未知数的信息:K
似乎是 str
而 V
似乎是 int
。所以此时,ranges
被推断为类型Dict[str, int]
.
接下来的两行会给出错误,因为空列表不能用作 Dict[str, int]
中的值,而 Dict[str, int]
中的值没有 [=23] =]方法。
显式类型注释 ranges: dict = dict()
通过指定这是一个异构字典来否决默认行为,因此值不必都具有相同的类型。鉴于该信息,静态分析器不会假设因为其中一个值是 int
它们都必须是 int
s.
由于您没有提供任何注释,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()
有人可以解释为什么这段代码虽然有效,但会导致 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]
形式,其中 K
和 V
尚未确定。第一个赋值 ranges['max'] = 0
给出了关于两个未知数的信息:K
似乎是 str
而 V
似乎是 int
。所以此时,ranges
被推断为类型Dict[str, int]
.
接下来的两行会给出错误,因为空列表不能用作 Dict[str, int]
中的值,而 Dict[str, int]
中的值没有 [=23] =]方法。
显式类型注释 ranges: dict = dict()
通过指定这是一个异构字典来否决默认行为,因此值不必都具有相同的类型。鉴于该信息,静态分析器不会假设因为其中一个值是 int
它们都必须是 int
s.
由于您没有提供任何注释,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()