如何键入一个可以接受列表或元组的列表或元组的函数?
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)
我有两个问题:
- 为什么 Mypy 会为此抛出错误?
- 我该怎么做才能获得我想要的功能(不会出现 Mypy 错误)?
(我不想只用 # type: ignore
禁用错误,我想定义一个可以工作的类型。)
我对 (1) 的猜测是它与 List
是 invariant 有关,但对于这个复杂的示例,我无法弄清楚如何。
除了可能实施前面提到的麻烦之外,我对 (2) 没有其他想法,这将使我摆脱 Union
s,我怀疑这可能是问题的一部分。
- 这确实与列表不变有关。当前版本的 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
兼容。因此错误。
- 正如 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的方式。
我正在尝试定义一个函数,它可以接受对象容器的容器,我不关心容器是元组还是列表。
(我知道我可以经历实施
所以,我有以下代码:
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)
我有两个问题:
- 为什么 Mypy 会为此抛出错误?
- 我该怎么做才能获得我想要的功能(不会出现 Mypy 错误)?
(我不想只用 # type: ignore
禁用错误,我想定义一个可以工作的类型。)
我对 (1) 的猜测是它与 List
是 invariant 有关,但对于这个复杂的示例,我无法弄清楚如何。
除了可能实施前面提到的麻烦之外,我对 (2) 没有其他想法,这将使我摆脱 Union
s,我怀疑这可能是问题的一部分。
- 这确实与列表不变有关。当前版本的 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
兼容。因此错误。
- 正如 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的方式。