【问题标题】:Type checking Python method with multiple possible inputs具有多个可能输入的类型检查 Python 方法
【发布时间】:2022-01-21 17:36:50
【问题描述】:

我有一个函数,它接受五个整数的列表或五个整数作为元组 (*argv)。

这是我的函数标题: def __init__(self, *argv: Union[int, list]) -> None:

稍后在这个函数中,我检查这个元组的内容,看看它是一个列表还是五个单独的整数。

        if type(argv[0]) == int:
            self._l1 = [argv[0], argv[1], argv[2], argv[3], argv[4]]
        else:
            self._l1 = argv[0]

此时代码中的 l1 是一个列表。 self._l1 绝对是一个列表,它不再是一个 int,它是一个列表。

但是稍后在我的代码中运行此行时:

        self._myvar = self._l1.count(1)

MyPy 向我返回了这个错误

赋值中的类型不兼容(表达式的类型为“Union[int, List[Any]]”,变量的类型为“List[Union[int, List[Any]]]”)

我输入错了吗,我需要输入什么?我尝试了很多不同的类型,并且不断出错。

据我所知,尽管我的输入是一个元组,其中包含一个整数列表或五个整数。我假设它类似于 Union[Tuple[List[int]], Tuple[int, ...]] 或只是 Union[List[int], int] 或 Union[List[int], Tuple[int, 。 ..]],或类似的东西,但这些都不适合我。

【问题讨论】:

  • 强烈建议更改您的班级,以便self._l1始终是一个列表,即使它只包含一项。尝试让一个函数做许多不同的事情只会导致不必要的复杂性。
  • 谢谢,但 self._l1 始终是一个列表,它是函数的参数,可以是整数元组或包含单个列表的元组。
  • 请显示导致错误的输入行。不清楚问题是当你传递 5 个整数时,还是当你传递一个元组时。错误消息是一个线索:注意它讨论了Union。您要确保 _li 变为 List[int]不是仍然包含 Union 的类型。在设置_li 的那4 行代码之后,添加执行print(type(self._li)) 的行。
  • 这仍然使您的函数变得不必要地复杂化:始终获取一个列表,并让调用者在必要时负责创建该列表。

标签: python types mypy typing typechecking


【解决方案1】:

无法找到您的版本无法正常工作的原因(因为 documentation 声明它应该适用于 type(...) is 语法,但在我的情况下,将 type 更改为 if isinstance(argv[0], int): 删除了您的mypy 错误。

【讨论】:

  • 文档对此有点不清楚,但问题是看到预期的行为。鉴于aX | Ytype(a) is X 将在if 内缩小aX。但是,它不会将a 缩小到else 内的Y。文档只声称前者,但问题试图依赖于后者。 isinstance(a, X) 但是会将a 缩小到if 内的Xelse 内的Y
  • 至于为什么type(a) is X不会在else中将a缩小为Y,这是因为如果aSubclassX,那么else仍然是已选择,但 issubclass(SubclassX, Y)False 并且在运行时访问 Ya 属性将在 else 中失败。
【解决方案2】:

我建议确保 _li 始终键入 List[int]

我没有测试下面的代码,但是cast 应该可以工作:

if isinstance(argv[0], int):
    self._l1 = [cast(int, argv[0]), cast(int, argv[1]), cast(int, argv[2]), cast(int, argv[3]), cast(int, argv[4])]
else:
    self._l1 = cast(List[int], argv[0])
print(type(self._li))

在哪里声明 _li 和 _myvar:

_li: List[int]
_myvar: List[int]

【讨论】:

  • 虽然这将满足mypy,但它可能在运行时失败,因为argv[0] 可能是int 的子类的实例,而不是List[int]。见Extending Base Classes。而是建议使用其他答案。
  • @MarioIshac - 很好的观点 - 我只是从问题中复制了它。我已经编辑了我的答案以使用isinstance。我相信 OP 最好同时使用isinstance 和向下转换为更简单的类型。这证明了输入是应该的,并避免了以后的类型问题。 (在问题中,OP 试图使 _myvar 或 _li 成为一些复杂的Union 类型。)
【解决方案3】:

为什么不使用typing.overload

看起来像这样:

from __future__ import annotations
from typing import overload

class MyClass:
    @overload
    def __init__(self: MyClass, a: int, b: int, c: int, d: int, e: int) -> None:
        ...

    @overload
    def __init__(self: MyClass, abcde: tuple[int,int,int,int,int]) -> None:
        ...

    def __init__(self, a, *bcde):
        if bcde:
            b, c, d, e = *bcde
            # etc
        else:
            a, b, c, d, e = *a
            # etc  

【讨论】:

    【解决方案4】:

    因此,您将argv 定义为带有* 的序列,它可以包含int 值或int 值列表。所以你真正想做的是“扁平化”序列。

    
    def flatten(alist):
        """Flatten a nested set of lists into one list."""
        rv = []
        for val in alist:
            if isinstance(val, (list, tuple)):
                rv.extend(flatten(val))
            else:
                rv.append(val)
        return rv
    
    class MyClass:
        def __init__(self, *argv: Union[int, list]):
            # argv is now a tuple of Union[int, list], or List[Union[int, List]], as the compiler says. 
            self._l1 = flatten(argv)
            # Now, you want to verify they are all ints, and that there are five.
            if len(self._l1) != 5:
                raise ValueError("Need five ints")
            if not all(isinstance(o, int) for o in self._l1):
                raise ValueError("not sequence of ints")
            # Now you have a single list of 5 ints.
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2011-09-29
      • 2016-10-03
      • 1970-01-01
      • 1970-01-01
      • 2022-01-26
      相关资源
      最近更新 更多