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

如何让Python的pipeline函数类型标注匹配最后一个函数的输出?

为Pipeline函数添加精确的类型标注

我想要编写一个pipeline函数,它接收任意数量的函数,将前一个函数的输出依次传递给下一个,直到最后一个。pipeline的输出是一个Callable,该Callable的输入与传入pipeline的第一个函数匹配,输出与传入的最后一个函数匹配。

目前的实现和类型标注如下:

from typing import TypeVar
from typing_extensions import ParamSpec, Concatenate

P = ParamSpec("P")
Output = TypeVar("Output")

def pipeline(func: Callable[Concatenate[P], T], *funcs: Callable[..., Output]) -> Callable[Concatenate[P], Output]:
    ...

但这会导致*funcs所有Output类型被合并为Union。举个例子:

from typing import Tuple

def double(x: int) -> Tuple[int, int]:
    return x, x*2

def add(x: int, y: int) -> int:
    return x + y

def to_string(x: int) -> str:
    return str(x)

new_func = pipeline(add, double, add, to_string) 

使用上述类型标注时,new_func的类型签名被错误推断为:

new_func: (x: int, y: int) -> (Tuple[int, int] | int | str) 

显然正确的签名应该是:

new_func: (x: int, y: int) -> str

请问是否有方法通过类型标注实现这一效果?


解决方案

要实现流水线函数的精确类型推断,需要让类型系统跟踪每一步函数的输入输出类型衔接。以下是两种可行方案:

方案一:递归泛型(兼容Python 3.10+)

借助typing_extensions的TypeVarTuple和Unpack,可以定义递归的类型约束,确保每个后续函数的输入匹配前一个函数的输出:

from typing import Callable, TypeVar, Tuple
from typing_extensions import ParamSpec, TypeVarTuple, Unpack

P = ParamSpec("P")
R = TypeVar("R")
Fs = TypeVarTuple("Fs")

def pipeline(
    first: Callable[P, R],
    *funcs: Unpack[Tuple[Callable[[R], R], ...]]
) -> Callable[P, R]:
    def composed(*args: P.args, **kwargs: P.kwargs) -> R:
        result = first(*args, **kwargs)
        for func in funcs:
            result = func(result)
        return result
    return composed

这个方案要求所有中间函数都是单参数的(接收前一个函数的输出作为唯一输入),完美契合流水线的执行逻辑。用示例测试时,new_func的类型会被正确推断为(x: int, y: int) -> str。

方案二:Python 3.12+ 泛型展开(更直观)

Python 3.12引入的泛型展开语法*,结合递归类型可以更清晰地跟踪每一步的类型转换:

from typing import Callable, TypeVar, ParamSpec
from typing_extensions import TypeVarTuple, Unpack

P = ParamSpec("P")
T = TypeVar("T")
Ts = TypeVarTuple("Ts")

def pipeline(
    f1: Callable[P, T],
    *funcs: Unpack[tuple[Callable[[T], Ts[0]], Callable[[Ts[0]], Ts[1]], ..., Callable[[Ts[-2]], Ts[-1]]]]
) -> Callable[P, Ts[-1]]:
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> Ts[-1]:
        res = f1(*args, **kwargs)
        for func in funcs:
            res = func(res)
        return res
    return wrapper

这个方案会让类型检查器严格验证每个函数的输入输出衔接关系,最终返回类型准确指向最后一个函数的输出类型。

注意事项

  • 确保类型检查器(如mypy、pyright)支持相关特性:mypy需开启--enable-recursive-aliases(Python 3.11+)或使用最新版本;pyright对这些特性的支持更友好。
  • 流水线中后续函数的输入必须与前一个函数的输出类型匹配,否则类型检查器会报错,这也能提前发现逻辑错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 03:20:32