*(拆包)运算符可以输入 Python 吗?或者任何其他可变参数函数使得所有可变类型都在结果类型中?

Can the * (unpacking) operator be typed in Python? Or any other variadic args function such that all variadic types are in the result type?

使用类型存根,我想知道是否可以在 Python 中表达一个类型,允许您为任意数量的参数正确键入:

def test(*args):
  return args

乍一看,我是:

T = TypeVar('T')
def test(*args: T) -> Tuple[T, ...]:
  return args

但这当然只会正确输入第一个 T。

是为所有参数编写覆盖的唯一可能方法吗?

T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
T4 = TypeVar('T4')

@overload
def test(arg1: T1) -> Tuple[T1]: ...
@overload
def test(arg1: T1, arg2: T2) -> Tuple[T1, T2]: ...
@overload
def test(arg1: T1, arg2: T2, arg3: T3) -> Tuple[T1, T2, T3]: ...
@overload
def test(arg1: T1, arg2: T2, arg3: T3, arg4: T4) -> Tuple[T1, T2, T3, T4]: ...
# etc
def test(*args: Any) -> Tuple[Any, ...]:
  return args

这也不完整,因为它没有携带足够的类型信息来键入如下内容:

x: Tuple[int, int, str] = test(*[1, 2, "4"])

TLDR:@overload 目前是注释 一些 方差水平的唯一可行方法。 PEP 646 -- Variadic Generics 是启用可变参数正确注释的提案草案。


注释 *args 的正确方法是确定支持的“方差长度”的某个级别,并使用 @overload 明确键入它。值得注意的是,显式参数必须只是位置参数——它们必须是 __anonymouspositional, /。最后一个包罗万象的可变参数 @overload 处理更多参数的情况。

from typing import TypeVar, Tuple, Any, overload

T = TypeVar('T')
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')

# positional parameters via `, /` – Python 3.8+ only
@overload
def test(arg1: T1, /) -> Tuple[T1]: ...
# positional parameters via double underscore prefix
@overload
def test(__arg1: T1, __arg2: T2) -> Tuple[T1, T2]: ...
@overload
def test(__arg1: T1, __arg2: T2, __arg3: T3) -> Tuple[T1, T2, T3]: ...
# etc
...
# catch all variadic signature for all other cases
@overload
def test(*args: T) -> Tuple[T, ...]: ...

# implementation can use Any to simplify matching all overloads
def test(*args: Any) -> Tuple[Any, ...]:
  return args

reveal_type(test(1, 2, "three"))     # note: Revealed type is 'Tuple[builtins.int*, builtins.int*, builtins.str*]'
reveal_type(test(1, 2, "three", 4))  # note: Revealed type is 'builtins.tuple[builtins.object*]'
reveal_type(test(1, 2, 3, 4))        # note: Revealed type is 'builtins.tuple[builtins.int*]'

值得注意的是,虽然 打包 可变参数可以键入,但 解包 参数通常不能:任何容器,但 tuple 是任意长度 - 例如 List[int] = [1, 2, 3] - 因此其元素没有确切的类型信息。

# unpack tuple of int, int
reveal_type(test(*(1, 2,)))  # note: Revealed type is 'Tuple[builtins.int*, builtins.int*]'
# unpack list of some ints
reveal_type(test(*[1, 2,]))  # note: Revealed type is 'builtins.tuple[builtins.int*]'