【问题标题】:Python mypy checking of return types for TypeVar(bound=Union[A, B]) doesn't error vs TypeVar(A, B) does errorPython mypy 检查 TypeVar(bound=Union[A, B]) 的返回类型不会出错 vs TypeVar(A, B) 会出错
【发布时间】:2020-08-03 17:16:39
【问题描述】:

当我以两种不同的方式使用 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 1: Success: no issues found
  • Case 2: 35: error: Incompatible return value type (got "Enum1", expected "Enum2")

这个案例非常类似于这个问题: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

并且当使用 case 2 (A, B) 时,以下是合法的:

  1. Enum1
  2. Enum2

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

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

【问题讨论】:

    标签: python generics static-analysis mypy


    【解决方案1】:

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

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

    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_enumEnum2valuetype 检查器通常不会触及的东西。可能会造成混淆,变量cast_enumEnum2,而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 没有为get_some_enum() 报告类型错误,这是因为它已决定Enums 类型与类型列表中的第一项相同。可以想象它保留了所有列出的类型的列表,并检查所有这些类型中的子类,就像它为 Union 类型所做的那样。那么问题就变成了这是有意还是无意?
    • @rocky 我认为问题在于 mypy 将_enum_to_num() 的返回类型推断为Enums 中的第一个类型,因此get_some_enum() 没有错误。但是这种行为听起来与我有关,而不是选择第一种类型,我宁愿 mypy 在_enum_to_num() 中警告用户它无法推断返回类型。
    • 谢谢@KenHung,这确实解释了我在EnumMeta 类型提示中出了什么问题,我现在可以让mypySuccess: no issues found
    • 是的@rocky 我同意你的说法“它已经决定Enums 类型与类型列表中的第一项相同”。如果有的话,我认为mypy 应该警告一些关于EnumMeta 不一定会导致Enums 类型的东西。我认为这可能不是错误,而更多的是尚未解决的边缘情况。
    【解决方案2】:

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

    消息:

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

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

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

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

    因此,如果您在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的bug,很难说……

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

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

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

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

    查看 Ken Hung 的回答,了解更明确的方法并消除 mypy 的错误。

    【讨论】:

    • 谢谢@rocky,这解释了正在发生的事情,我想它回答了我对Can anyone please tell me what's going on? 的问题。但是,它并没有解释为什么mypy 有这个问题。这似乎是mypy 中的一个错误,你同意吗?换句话说,我仍然认为mypy 不应该与Case 2 引发错误。
    • @IntrastellarExplorer 我已经修改了 start 的答案,以解决这是否是 mypy 错误的问题。
    • 感谢您编辑/改进@rocky,我还根据您的建议更新了问题的措辞。我希望我能把赏金分成两半。
    • @IntrastellarExplorer 我不关心这些点。很高兴问题已经解决。
    猜你喜欢
    • 2020-08-17
    • 2021-04-18
    • 1970-01-01
    • 2012-07-26
    • 2021-05-15
    • 2019-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多