为什么 mypy 不从@overload 推断函数注解?

Why does mypy not infer function annotation from @overload?

I 运行 mypy 带有 disallow-untyped-defs 选项。当我用@overload 注释函数类型并在定义中省略注释时,mypy 仍然会产生错误。对我来说,这个函数似乎应该被认为是带注释的。

example.py:

from typing import overload
@overload
def f(arg: int) -> int: ...
@overload
def f(arg: str) -> str: ...

def f(arg):
    if type(arg) == int:
        return 1
    elif type(arg) == str:
        return "a"
    else:
        raise ValueError

命令行:

mypy --disallow-untyped-defs example.py

输出:

example.py:7: error: Function is missing a type annotation
Found 1 error in 1 file (checked 1 source file)

这是有意为之的行为。 Mypy 希望您将类型注释添加到重载的实现中,如下所示:

from typing import overload, Union

@overload
def f(arg: int) -> int: ...
@overload
def f(arg: str) -> str: ...
def f(arg: Union[int, str]) -> Union[int, str]:
    if type(arg) == int:
        return 1
    elif type(arg) == str:
        return "a"
    else:
        raise ValueError

这样,mypy 仍将拥有成功对 f 的主体进行类型检查所需的所有信息。

mypy 之所以不根据重载类型签名自动推断实现类型签名或尝试使用重载签名对实现进行两次类型检查,是因为这两个签名通常最终会彼此非常不同.

例如,这是一个更复杂的重载示例:

@overload
def zip(/, i1: Iterable[_T1]) -> Iterator[Tuple[_T1]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2]) -> Iterator[Tuple[_T1, _T2]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2],
        i3: Iterable[_T3]) -> Iterator[Tuple[_T1, _T2, _T3]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2], i3: Iterable[_T3],
        i4: Iterable[_T4]) -> Iterator[Tuple[_T1, _T2, _T3, _T4]]: ...
@overload
def zip(/, i1: Iterable[_T1], i2: Iterable[_T2], i3: Iterable[_T3],
        i4: Iterable[_T4], i5: Iterable[_T5]) -> Iterator[Tuple[_T1, _T2, _T3, _T4, _T5]]: ...
@overload
def zip(/, i1: Iterable[Any], i2: Iterable[Any], i3: Iterable[Any],
        i4: Iterable[Any], i5: Iterable[Any], i6: Iterable[Any],
        *remainder: Iterable[Any]) -> Iterator[Tuple[Any, ...]]: ...
def zip(*iterables: Iterable[Any]) -> Iterator[Tuple[Any, ...]]:
    sentinel = object()
    iterators = [iter(it) for it in iterables]
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem is sentinel:
                return
            result.append(elem)
        yield tuple(result)

对于 mypy 来说,推断这个例子中的实现签名应该是什么是非常具有挑战性的:参数的数量不匹配,弄清楚如何处理所有的 TypeVars(保留它们?丢弃它们)他们?)很棘手...

这些都是 mypy 理论上可以解决的问题,但在漫长的 运行 中,它是否真的为用户节省了那么多时间还不清楚。在像这样的复杂情况下,用户实际上可能更愿意能够准确说明实现签名是什么。

(如果一个问题 (a) 很难并且 (b) 是一个小问题,它往往无法解决,尤其是在开源项目中。)

因此,为了保持体验的一致性,mypy 不会尝试在每种情况下自动推断实现签名。