如何键入一个可以接受列表或元组的列表或元组的函数?

How to type a function that can take a list or tuple of a list or tuple?

我正在尝试定义一个函数,它可以接受对象容器的容器,我不关心容器是元组还是列表。

(我知道我可以经历实施 的麻烦,但我不想,而且我仍然不确定这是否能解决我的问题。)

所以,我有以下代码:

from typing import Any, Union, List, Tuple

# (list or tuple) of Any
AnyBucket = Union[List[Any], Tuple[Any, ...]]
# should be (list or tuple) of (lists or tuples) of Any
AnyBuckets = Union[List[AnyBucket], Tuple[AnyBucket, ...]]

def takes_any_buckets(inpt: AnyBuckets) -> None:
    print(inpt[0][0])

input_value: List[List[Any]] = [[1], [2]]

# Argument 1 to "takes_any_buckets" has incompatible type "List[List[Any]]";
# expected "Union[List[Union[List[Any], Tuple[Any, ...]]], Tuple[Union[List[Any], Tuple[Any, ...]], ...]]"
takes_any_buckets(input_value)

我有两个问题:

  1. 为什么 Mypy 会为此抛出错误?
  2. 我该怎么做才能获得我想要的功能(不会出现 Mypy 错误)?

(我不想只用 # type: ignore 禁用错误,我想定义一个可以工作的类型。)

我对 (1) 的猜测是它与 Listinvariant 有关,但对于这个复杂的示例,我无法弄清楚如何。

除了可能实施前面提到的麻烦之外,我对 (2) 没有其他想法,这将使我摆脱 Unions,我怀疑这可能是问题的一部分。

  1. 这确实与列表不变有关。当前版本的 Mypy 会告诉您这一点;如果您 运行 当前版本的代码,您会收到以下注释以及错误:
main.py:14: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
main.py:14: note: Consider using "Sequence" instead, which is covariant

列表不变意味着 A 类型的列表是 B 类型列表的子类型当且仅当 A 等于 B。这意味着 即使 A 是 B 的子类型,List[A] 也不是 List[B] 的子类型。 (参见此处,了解不变性的解释以及 mypy 将列表视为不变的原因:https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types)。

所以 List[List[Any]] 不是 List[AnyBucket] 的子类型,即使 List[Any]AnyBucket 的子类型,所以 input_value 没有类型与 AnyBuckets 兼容。因此错误。

  1. 正如 Mypy 和 ShadowRanger 都在上面推荐的那样,您可能应该使用 Sequence,一种涵盖可被索引到的可迭代对象的协变类型(到第一个近似值——还有一些其他的东西需要一个集合为真,因为它是一个序列)。您需要从 collections.abc 导入它;看这里:https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence

我说“可能”是因为它完全取决于您要键入的函数对集合的作用。例如,如果您所做的只是遍历集合(一次),那么您想使用 Iterable,一种更通用的类型。

使用类似 Sequence 的优点是,如果有任何其他类型的对象漂浮在那里——也许是用户定义的对象——可以完成你想要的工作,它们将是有效的输入到您的函数(只要它们已注册为 Sequences)。一般来说,能用structural typing, which is the static-typing version equivalent of duck-typing就好了,也就是Python的方式。