问题应该很简单。我有一个接受字典的函数,其中值必须是 float、int 或 numpy.ndarray 类型。我可以使用 ArrayLike 类型。 在里面,我有两个函数专门处理标量值(float、int)或数组值。 这是一个代码示例:
from typing import Dict, Union, Mapping
import numpy as np
from numpy.typing import ArrayLike
def fun_for_float(data: dict[str, Union[float,int]]) -> None:
pass
def fun_for_array(data: dict[str, np.ndarray]) -> None:
pass
def main_function(data: dict[str, ArrayLike]) -> None:
if all(isinstance(val, Union[float,int]) for val in data.values()):
return fun_for_float(data)
elif all(isinstance(val, np.ndarray) for val in data.values()):
return fun_for_array(data)
else:
raise ValueError("Values in the dictionary must be float or np.ndarray.")
问题是,pyright 接缝不理解我的类型守卫。 我收到以下错误:
Argument of type "dict[str, ArrayLike]" cannot be assigned to parameter "data" of type "dict[str, float | int]" in function "fun_for_float"
"dict[str, ArrayLike]" is incompatible with "dict[str, float | int]"
Type parameter "_VT@dict" is invariant, but "ArrayLike" is not the same as "float | int"
Consider switching from "dict" to "Mapping" which is covariant in the value type
现在,我明白了。字典使用不变值,好吧。 ArrayLike 肯定与 float 或 int 不同,但是 float 和 int 应该包含在其中,并且通过 if 语句,pyrigth 应该能够缩小数据类型。
无论如何,我遵循了pyright的建议并使用了Mapping。
def fun_for_float(data: Mapping[str, Union[float,int]]) -> None:
pass
def fun_for_array(data: Mapping[str, np.ndarray]) -> None:
pass
def main_function(data: Mapping[str, ArrayLike]) -> None:
if all(isinstance(val, Union[float,int]) for val in data.values()):
return fun_for_float(data)
elif all(isinstance(val, np.ndarray) for val in data.values()):
return fun_for_array(data)
else:
raise ValueError("Values in the dictionary must be float or np.ndarray.")
这就是我得到的。
Argument of type "Mapping[str, ArrayLike]" cannot be assigned to parameter "data" of type "Mapping[str, float | int]" in function "fun_for_float"
"Mapping[str, ArrayLike]" is incompatible with "Mapping[str, float | int]"
Type parameter "_VT_co@Mapping" is covariant, but "ArrayLike" is not a subtype of "float | int"
现在,如果没有类型保护,pyright 是 100% 正确的,它应该抱怨。 ArrayLike 绝对不是 Union[float | int],事实恰恰相反。
有两个原因可以解释该错误:
all(isinstance()...)
类型检查。有什么建议吗?
问题发布后不久,我意外地解决了问题。 如果有人需要的话,我将答案留在这里。 假设这两种可能性在某种程度上都是正确的。 Pyright 无法自主解释
all(asinstance())
结构。而且我搞砸了,不知道你可以定义明确的 TypeGuards(我没有阅读文档的错)。
为了帮助pyright,可以构造显式类型保护。
这是一个更简单的例子
def is_float(data: List[int|float]) -> TypeGuard[List[float]]:
return all(isinstance(val, float) for val in data)
def is_int(data: List[int|float]) -> TypeGuard[List[int]]:
return all(isinstance(val,int) for val in data)
def handle_int_list(data: List[int]) -> None:
pass
def handle_float_list(data: List[float]) -> None:
pass
def process_data(data: List[Union[int, float]]) -> None:
if is_int(data):
handle_int_list(data)
elif is_float(data):
handle_float_list(data)
else:
print("Data contains mixed types")