Python mypy 检查 TypeVar(bound=Union[A, B]) 的 return 类型不会出错,而 TypeVar(A, B) 会出错

Python mypy checking of return types for TypeVar(bound=Union[A, B]) doesn't error vs TypeVar(A, B) does error

当我以两种不同的方式使用它时,我一直试图理解 TypeVar 的边界:

这是我使用的代码:

#!/usr/bin/env python3.6

"""Figuring out why enum is saying incompatible return type."""


from enum import IntEnum, EnumMeta
from typing import TypeVar, Union


class Enum1(IntEnum):

    MEMBER1 = 1
    MEMBER2 = 2


class Enum2(IntEnum):

    MEMBER3 = 3
    MEMBER4 = 4


# Enums = TypeVar("Enums", bound=Union[Enum1, Enum2])  # Case 1... Success
Enums = TypeVar("Enums", Enum1, Enum2)  # Case 2... error: Incompatible return value


def _enum_to_num(val: int, cast_enum: EnumMeta) -> Enums:
    return cast_enum(val)


def get_some_enum(val: int) -> Enum1:
    return _enum_to_num(val, Enum1)


def get_another_enum(val: int) -> Enum2:
    return _enum_to_num(val, Enum2)  # line 35

当运行 mypy==0.770:

这个案例非常类似于这个问题:Difference between TypeVar('T', A, B) and TypeVar('T', bound=Union[A, B])

答案解释当使用案例1(bound=Union[Enum1, Enum2])时,以下是合法的:

  1. Union[Enum1, Enum2]
  2. Enum1
  3. Enum2

而当使用情况 2 (A, B) 时,以下是合法的:

  1. Enum1
  2. Enum2

但是,我不认为这个答案解释了我的问题,我没有使用 Union 案例。

谁能告诉我这是怎么回事?

我先写一点关于 mypy 看到和报告的内容,然后是这是否是 mypy 错误的问题。

留言:

Incompatible return value type (got "Enum1", expected "Enum2")

在这里意味着粗略地 Enum2 或它的子类型是预期的。 Enum2get_another_enum() 声明的 return 值。然而 mypy 认为函数调用 _enum_to_num() 是 returning 一个 Enum1 类型。

"roughly"部分是因为当类型未绑定,或者是Any,或者Union类型时,类型检查存在异常;但这不适用于此示例。

Mypy 决定 _enum_to_num() 中的函数 cast_enum() 是 returning Enums 中列出的 first 类型 — 我猜测作为静态类型检查器,它必须选择一个,这就是它所做的。

因此,如果您在 Enums 作业中调换顺序并写入:

Enums = TypeVar("Enums", Enum2, Enum1)  # Case 2... error: Incompatible return value

然后第 35 行将成功,但 get_some_enum() 中的 return 将失败并显示消息:

error: Incompatible return value type (got "Enum2", expected "Enum1")

至于这是不是mypy的bug,不好说...

这里没有使用type()ininstance()函数可以找到的动态类型错误; 运行ning 代码也按预期工作。

另一方面,Python 从不在编译时或 运行 时检查 return 类型:您可以更改 return 类型 _enum_to_none() 变为 None 并且就 Python 解释器而言仍然有效。

那么问题归结为:在mypy强加的静态类型系统中,这是一个错误吗? (我不认为 PEP 484、526 或其他数字试图解决这个问题)。

更有资格的人应该回答这个问题,这是否是一个应该由静态分析器(尤其是 mypy)捕获的错误。

请参阅 Ken Hung 的回答以获得更明确的方法并消除 mypy 的错误。

我认为错误的发生是因为类型检查器没有足够的信息来通过查看输入参数的类型来推断 return 类型。虽然处理可能会有所改善。

假设您有一个简单的通用函数:

Enums = TypeVar("Enums", Enum1, Enum2)

def add(x: Enums, y: Enums) -> Enums:
    return x

类型检查器可以通过 输入参数的类型 :

推断 return 类型
add(Enum2.MEMBER3, Enum2.MEMBER4) # ok, return Enum2
add(Enum1.MEMBER1, Enum1.MEMBER2) # ok, return Enum1

add(Enum2.MEMBER3, Enum1.MEMBER2) # not ok

再看看你的函数_enum_to_num,类型检查器无法推断return类型,它只是不知道return是什么类型,因为它不知道 return 会被 cast_enum:

编辑成什么类型
def _enum_to_num(val: int, cast_enum: EnumMeta) -> Enums:
    return cast_enum(val)

静态类型检查的思想是它评估代码而不执行,它调查变量的 types,而不是动态的 values.通过查看 cast_enum 的类型,即 EnumMeta,类型检查器无法判断 cast_enum 是否会 return Enums。看起来它只是假设它会 return Enum1,它会导致 _enum_to_num(val, Enum2).

中的错误

你知道 _enum_to_num(val, Enum2) 会 return Enum2 因为你知道 cast_enumEnum2. valuetype 检查器一般不会触及的东西。可能会混淆,变量cast_enumvalueEnum2,而[=15=的type ] 是 EnumMeta,尽管 Enum2 类型

这个问题可以通过告诉类型检查器类型将通过 cast_enum 使用 typing.Type:

来解决
from typing import TypeVar, Union, Type

...

def _enum_to_num(val: int, cast_enum: Type[Enums]) -> Enums:
    return cast_enum(val)

错误将消失,因为现在类型检查器可以推断出 return 类型。