You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Python泛型函数返回类型基于可变位置参数的最后一项?

解决可变位置参数函数的最后一个参数类型标注问题

你需要实现的foo函数是一个链式调用函数,接收1个或多个可调用对象,返回类型与最后一个可调用对象的返回类型一致。要在保持所有参数为位置参数的前提下完成类型提示,可以分版本采用不同方案:

Python 3.11+:使用TypeVarTuple和Unpack实现完整可变参数类型推断

Python 3.11引入的TypeVarTuple和Unpack特性,能完美处理可变数量的类型序列,同时精准追踪链式调用的类型衔接:

from typing import TypeVar, Callable, TypeVarTuple, Unpack

T0 = TypeVar("T0")
T = TypeVar("T")
Intermediate = TypeVarTuple("Intermediate")

def foo(
    first: Callable[[], T0],
    *rest: Unpack[tuple[Callable[[T0], Intermediate[0]], Callable[[Intermediate[0]], Intermediate[1]], ..., Callable[[Intermediate[-1]], T]]]
) -> T:
    result = first()
    for fn in rest:
        result = fn(result)
    return result

这个定义让类型检查器自动追踪每一步的类型逻辑:

  • 第一个函数必须是无参可调用,返回类型为T0
  • 后续每个函数都必须接收前一个函数的返回类型作为输入
  • 最后一个函数的返回类型T就是整个foo函数的返回类型

测试调用时,类型推断完全符合预期:

foo(lambda: "hello")             # 推断返回类型为 str
foo(lambda: "hello", str.upper)  # 推断返回类型为 str
foo(lambda: "hello", len)        # 推断返回类型为 int

兼容Python 3.10及以下:使用@overload覆盖常用参数数量

如果需要支持更低版本的Python,只能通过@overload定义多个显式签名,覆盖常见的参数数量场景:

from typing import TypeVar, Callable, overload

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

@overload
def foo(fn: Callable[[], T]) -> T: ...

@overload
def foo(fn1: Callable[[], T1], fn2: Callable[[T1], T]) -> T: ...

@overload
def foo(fn1: Callable[[], T1], fn2: Callable[[T1], T2], fn3: Callable[[T2], T]) -> T: ...

# 可根据需求继续添加更多重载,比如4个、5个参数的情况
def foo(*fns):
    result = fns[0]()
    for fn in fns[1:]:
        result = fn(result)
    return result

这种方法的缺点是参数数量受限于你定义的重载数量,但能在低版本Python中提供准确的类型提示。

为什么最初的方案行不通?

Python语法规定,*可变位置参数之后的参数默认是关键字-only参数(PEP 3102),所以你尝试的def foo(*fns: Callable[..., Any], fnN: Callable[..., T]) -> T会强制fnN必须通过关键字传递,无法作为位置参数的最后一项,这不符合你的调用需求。

内容的提问来源于stack exchange,提问作者frans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:13:17