`@overload`ed 函数参数的模式匹配中的 mypy case 耗尽

问题描述

我有一个 Python 类 MyClass,有三种可接受的方式来实例化它:

  • 直接使用 BitVector;
  • 使用 intbytes 表示位向量的大小和内容
  • 使用 intstr 表示位向量的大小和内容

使用类型注释,特别是 @overload 装饰器,我确保构造函数不会被滥用。 最重要的是,为了确保我不会忘记任何情况,我实施了详尽的模式匹配。

总的来说,它看起来如下:

from typing import overload,noreturn,Optional,Union

def assert_never(value: noreturn) -> noreturn:
    assert False,f'Unhandled value: {value} ({type(value).__name__})'

class BitVector(): ...

class MyClass:

    @overload
    def __init__(self,*,size: int,content: bytes): ...
    @overload
    def __init__(self,content: int): ...
    @overload
    def __init__(self,bv: BitVector): ...

    def __init__(self,bv: Optional[BitVector]=None,size: Optional[int]=None,content: Optional[Union[bytes,int]]=None):
        if isinstance(size,int) and isinstance(content,(bytes,int)):
            # instanciate a BitVector and do something
            ...
        elif isinstance(bv,BitVector):
            # do something with it
            ...

        else:
            assert_never((bv,size,content))

但是,在运行 mypy 时,出现以下错误

example.py:25: error: Argument 1 to "assert_never" has incompatible type "Tuple[None,Optional[int],Union[bytes,int,None]]"; expected "noreturn"
Found 1 error in 1 file (checked 1 source file)

就好像mypy只依赖实现函数的签名,没有考虑@overloads声明的类型约束...

如何对 @overload 声明所声明的参数可以采用的唯一类型组合执行详尽的模式匹配?

解决方法

你不应该给真正的 def 函数添加签名:

@overload
def __init__(self,*,size: int,content: Union[bytes,int]): ...

@overload
def __init__(self,bv: BitVector): ...

def __init__(self,bv=None,size=None,content=None):

我在真正的 def 中添加了 * 以遵循重载签名。