关于类型类型的 Mypy 断言

Mypy Assertions on the Types of Types

在Python中,我们可以这样给一个变量赋值类型,并通过mypy的typecheck:

from typing import Type, Union


class Foo:
    pass


MyType: Type[Foo] = Foo

同样,我们也可以使用Union进行打字

from typing import Type, Union


class Foo:
    pass


class Bar:
    pass


def func(is_bar: bool) -> Union[Type[Foo], Type[Bar]]:
    if is_bar:
        return Bar
    else:
        return Foo

知道 is_bar 的值,在调用 func 进行类型缩小后使用断言是完全合理的。

但是,断言似乎对 MyType 根本不起作用,因为 mypy 对以下代码示例给出错误:

MyType = func(True)

assert MyType is Bar  
# `assert MyType is Type[Bar]` gives the same result

Test: Bar = MyType  # Incompatible types in assignment (expression
                    # has type "Union[Type[Foo], Type[Bar]]",
                    # variable has type "Bar")

castisinstance 也不起作用。

MyType = func(True)
cast(Type[Bar], MyType)
Test: Bar = MyType  # MyType is considered `Type[Bar] | Type[Foo]`
MyType = func(True)
assert isintance(MyType, Type[Bar])
Test: Bar = MyType  # MyType is considered `Type[Bar] | Type[Foo]`

我的问题是:如何对类型的类型进行类型缩小?还是mypy的限制?如果是这样,解决方法是什么?

相关:

cast 助手 returns 它的约束参数,它实际上并没有 改变 它的参数是约束。

cast 到所需的 Type[...] 并分配或使用结果:

Test = cast(Type[Bar], MyType)
reveal_type(Test)  # note: Revealed type is "Type[so_testbed.Bar]"

另一种避免必须使用 typing.castisinstance 的解决方案是使用 typing.overload,它允许您注册单个函数的多个签名。所有用 @typing.overload 装饰的函数在运行时都会被忽略,以支持“具体”实现,因此这些函数的主体可以只是一个文字省略号。通过组合 typing.overloadtyping.Literal,我们可以注册函数的一个签名,如果传入值 True,如果传入值 False 则注册另一个:

from typing import Type, Union, overload, Literal


class Foo:
    pass


class Bar:
    pass


@overload
def func(is_bar: Literal[True]) -> Type[Bar]: ...


@overload
def func(is_bar: Literal[False]) -> Type[Foo]: ...


def func(is_bar: bool) -> Union[Type[Foo], Type[Bar]]:
    if is_bar:
        return Bar
    else:
        return Foo
        

test: Type[Bar] = func(True)
test2: Type[Foo] = func(False)
test3: Bar = func(True)()
test4: Foo = func(False)()

类型检查没问题。