Returning a `tuple[str, str]` from `str.split(s, maxsplit=1)



我有这个函数:

__version__: str | None = ...
def get_version_parts() -> tuple[str, str] | None:
if __version__ is None:
return None
return tuple(__version__.split('+', maxsplit=1))

不出所料,Mypy没有意识到这里的返回值确实是tuple[str, str],而是认为它是tuple[str, ...]

我必须在这里使用typing.cast吗,或者有其他方法来解决这个问题吗?

我只想说两点:元组的长度不是静态已知的。它在运行时str.split方法的输出确定。

当然,str.split的签名并没有告诉类型检查器它返回的列表的长度,因为这甚至不可能进行注释。(通用list只接受一个类型参数。(

即使如果str.split返回了一个元组,可以对其进行注释以指示其长度,那么仍然不可能覆盖字面意义上的无穷大的情况。以下是一个如何使用自定义split_str函数实现理论的示例:

from typing import Literal, overload
@overload
def split_str(s: str, maxsplit: Literal[-1]) -> tuple[str, ...]:
...
@overload
def split_str(s: str, maxsplit: Literal[0]) -> tuple[str]:
...
@overload
def split_str(s: str, maxsplit: Literal[1]) -> tuple[str, str]:
...
@overload
def split_str(s: str, maxsplit: Literal[2]) -> tuple[str, str, str]:
...
def split_str(s: str, maxsplit: int = -1) -> tuple[str, ...]:
return tuple(s.split(maxsplit=maxsplit))
def foo() -> tuple[str, str, str]:
return split_str("a b c", maxsplit=2)

当然,问题是除了-1012之外,没有任何maxsplit值的变体。任何其他呼叫都会导致mypy投诉。


所有mypytuple("a b c".split(maxsplit=1))一起看到的是tuple接收list[str](其对应于期望的Iterable[T](,因此tuple[str, ...]是出来的。一般来说,Iterable甚至不具有长度,因此也没有任何其他方法来说明tuple转换。

尽管我承认以某种方式将tuple.__len__断言与tuple的类型自变量联系起来会很好。我想这会打开另一个蠕虫罐头。

虽然mypy可能会传递一个拆包分配,但它仍然会更新它对拆包元组规范的看法:

t = tuple([1, 2])
x, y = t  # no complaints
reveal_type(t)  # note: Revealed type is "builtins.tuple[builtins.int, ...]"

谁知道呢,也许这种情况将来会改变。

编辑:我忘记了字符串不包含+的情况。则只生成一个值,而不是两个。信用证;Ry-";在评论中指出了这一点。

我要离开这个职位并回答,因为我认为;打开包装,然后返回";这个成语可能对其他人有用。


一个对小元组有意义但会产生(微小(运行时成本的解决方法是显式解压缩元组:

def get_version_parts() -> tuple[str, str] | None:
if __version__ is None:
return None
calendar, revision = __version__.split('+', maxsplit=1)
return calendar, revision

我按照assert len(result) == 2的思路尝试了其他一些组合,但这是唯一有效的。

Mypy显然无法将tuple[str, ...]的类型缩小到tuple[str, str],即使其长度是静态已知的。

也许这将是一个有趣的功能请求,但我现在对开箱解决方案感到满意。

最新更新