具有多个可能输入的类型检查 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 错误。
【讨论】:
文档对此有点不清楚,但问题是看到预期的行为。鉴于a
是X | Y
,type(a) is X
将在if
内缩小a
到X
。但是,它不会将a
缩小到else
内的Y
。文档只声称前者,但问题试图依赖于后者。 isinstance(a, X)
但是会将a
缩小到if
内的X
和else
内的Y
。
至于为什么type(a) is X
不会在else
中将a
缩小为Y
,这是因为如果a
是SubclassX
,那么else
仍然是已选择,但 issubclass(SubclassX, Y)
是 False
并且在运行时访问 Y
的 a
属性将在 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 方法的主要内容,如果未能解决你的问题,请参考以下文章