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



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

  • Enums = TypeVar("Enums", Enum1, Enum2)
  • Enums = TypeVar("Enums", bound=Union[Enum1, Enum2])

这是我正在使用的代码:

#!/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

  • Case 1Success: no issues found
  • Case 235: error: Incompatible return value type (got "Enum1", expected "Enum2")

这种情况与这个问题非常相似:TypeVar('T', A, B) 和 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的情况。

谁能告诉我发生了什么?

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

假设你有一个简单的泛型函数:

Enums = TypeVar("Enums", Enum1, Enum2)
def add(x: Enums, y: Enums) -> Enums:
return x

类型检查器可以通过输入参数的类型推断返回类型:

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,类型检查器没有办法推断返回类型,它只是不知道会返回什么类型,因为它不知道cast_enum会返回什么类型:

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

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

您知道_enum_to_num(val, Enum2)会返回Enum2因为您知道cast_enum的值Enum2该值类型检查器通常不会触及的值。可能会令人困惑,变量cast_enum的值是Enum2,而cast_enum的类型EnumMeta的,尽管Enum2是一种类型

此问题可以通过告诉类型检查器类型将使用typing.Type通过cast_enum来解决此问题:

from typing import TypeVar, Union, Type
...
def _enum_to_num(val: int, cast_enum: Type[Enums]) -> Enums:
return cast_enum(val)

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

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

消息:

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

这里的意思大致是预期的Enum2或子类型。Enum2是声明的返回值get_another_enum()。但是mypy认为函数调用_enum_to_num()返回的是Enum1类型。

"大致"部分是因为当类型未绑定时,或者是一个Any,或者Union类型时,类型检查有例外;但这不适用于此示例。

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

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

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

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

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

至于这是否是mypy虫,很难说...

使用type()ininstance()函数可以在此处找到动态类型错误;运行代码也按预期工作。

另一方面,Python 从不检查返回类型,无论是在编译时还是在运行时:您可以将_enum_to_none()的返回类型更改为None,就 Python 解释器而言,这仍然有效。

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

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

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

最新更新