mypy return 各种长度的元组类型

mypy return type of tuples of various length

这是一些模拟代码:

def my_func() -> set[tuple[str, ...]]:
    my_set: set[tuple[str, str]] = set()
    # do some stuff
    return my_set

运行 上面代码中的 mypy 给出了错误:

Incompatible return value type (got "Set[Tuple[str, str]]", expected "Set[Tuple[str, ...]]")

为什么 set[tuple[str, str]] 不被识别为 set[tuple[str, ...]]

在生产中,我有几个上述形式的函数,每个函数 return 是一组同质元组(my_func2 将 return 一组 5 元组,并且my_func3 会 return 一组 11-tuples)。我希望 return 类型与我的函数系列 my_func, my_func2, my_func3.

相同

set 是不变的,这意味着类型参数必须完全匹配,而不是一个简单地符合另一个。这里最简单的修复是只用所需的 return 类型注释 my_set

def my_func() -> set[tuple[str, ...]]:
    my_set: set[tuple[str, ...]] = set()
    my_set.add(("foo", "bar"))
    return my_set

另一个选项(如果你希望 my_set 被限制在函数体内的 2 元素元组)将是 cast 它当你 return:

from typing import cast

def my_func() -> set[tuple[str, ...]]:
    my_set: set[tuple[str, str]] = set()
    my_set.add(("foo", "bar"))
    return cast(set[tuple[str, ...]], my_set)

但是,请注意集合不变的原因是因为它们是可变的。假设您将 my_set (作为一组 2 元组)传递给另一个(更挑剔的)函数,该函数期望它只包含 2 元组,然后 return 对 my_set 的引用已转换为可以包含 n-tuples。现在任何拥有该转换引用的人都可以将 n-tuples 插入其中,这违反了更挑剔的函数的类型假设!这就是为什么 cast 可能是危险的——在这种情况下,只有在 return.

之后没有对 my_set 及其 2 元组类型的引用仍然存在时,它才是安全的

所以如果你想my_set被打成一个二元组,但是你想return一组n-tuples,另一种选择是return frozenset:

def my_func() -> frozenset[tuple[str, ...]]:
    my_set: set[tuple[str, str]] = set()
    my_set.add(("foo", "bar"))
    return frozenset(my_set)

返回 frozenset 可确保调用者无法改变 my_set 并违反其二元组类型。

请注意,return 匿名(可变)副本也非常安全(我认为),但 mypy 不会推断出这一点,因此您仍然必须 cast 明确地:

    return cast(set[tuple[str, ...]], my_set.copy())