具有多个可能输入的类型检查 Python 方法

Posted

技术标签:

【中文标题】具有多个可能输入的类型检查 Python 方法【英文标题】:Type checking Python method with multiple possible inputs 【发布时间】: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)) 的行。 这仍然使您的函数变得不必要地复杂化:始终获取一个列表,并让调用者在必要时负责创建该列表。 【参考方案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.

【讨论】:

以上是关于具有多个可能输入的类型检查 Python 方法的主要内容,如果未能解决你的问题,请参考以下文章

如何根据T创建不同行为的泛型方法?

PYTHON 中可能输入的函数

C# 调用具有不同类型的相同扩展函​​数作为参数(可能是委托?)

2.使用Python解释器

检查多个接受期间的日期期间(例如许可期间与使用期间)

因果图法