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

如何为柯里化函数添加符合mypy要求的类型注解?

柯里化函数类型注解适配mypy的解决方法

问题描述

使用Concatenate和ParamSpec实现柯里化函数时,mypy在调用柯里化后的函数(如f(1))时报错,但相同代码在pyright中可正常识别,需要修改类型注解以通过mypy检查。

原问题代码

from typing import TypeVar, ParamSpec, Concatenate
from typing import Callable as Fn, reveal_type

P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")


def curry(f: Fn[Concatenate[T, P], R]) -> Fn[[T], Fn[P, R]]:
    """An attempt at currying."""

    def outer(x: T) -> Fn[P, R]:
        def inner(*args: P.args, **kwargs: P.kwargs) -> R:
            return f(x, *args, **kwargs)

        return inner

    return outer


@curry
def f(x: T, y: int) -> T:
    """Test function."""
    return x


def g(x: T, /) -> Fn[[int], T]:
    """Test function."""
    return lambda _: x

reveal_type(f)
reveal_type(g)
reveal_type(f(1)) # <- this fails in mypy
reveal_type(g(1))

问题原因

mypy对装饰器中泛型与ParamSpec的结合处理逻辑与pyright不同,会过早绑定泛型变量T,导致调用柯里化后的函数时无法正确推断后续参数和返回值类型。

解决方案

方案1:改用显式泛型函数定义(Python 3.12+)

使用PEP 695引入的泛型函数语法,明确标记目标函数为泛型,帮助mypy正确追踪类型变量:

from typing import TypeVar, ParamSpec, Concatenate
from typing import Callable as Fn, reveal_type

P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")


def curry(f: Fn[Concatenate[T, P], R]) -> Fn[[T], Fn[P, R]]:
    """An attempt at currying."""

    def outer(x: T) -> Fn[P, R]:
        def inner(*args: P.args, **kwargs: P.kwargs) -> R:
            return f(x, *args, **kwargs)

        return inner

    return outer


@curry
def f[T](x: T, y: int) -> T:  # 显式声明泛型
    """Test function."""
    return x


def g(x: T, /) -> Fn[[int], T]:
    """Test function."""
    return lambda _: x

reveal_type(f)          # 推断为: Callable[[T], Callable[[int], T]]
reveal_type(g)          # 推断为: Callable[[T], Callable[[int], T]]
reveal_type(f(1))       # 推断为: Callable[[int], int]
reveal_type(g(1))       # 推断为: Callable[[int], int]

方案2:替换装饰器语法为显式调用

避免装饰器语法导致的泛型过早绑定,直接调用curry函数包装目标函数:

from typing import TypeVar, ParamSpec, Concatenate
from typing import Callable as Fn, reveal_type

P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")


def curry(f: Fn[Concatenate[T, P], R]) -> Fn[[T], Fn[P, R]]:
    """An attempt at currying."""

    def outer(x: T) -> Fn[P, R]:
        def inner(*args: P.args, **kwargs: P.kwargs) -> R:
            return f(x, *args, **kwargs)

        return inner

    return outer


# 不用装饰器,显式调用curry包装函数
def f(x: T, y: int) -> T:
    """Test function."""
    return x
f = curry(f)


def g(x: T, /) -> Fn[[int], T]:
    """Test function."""
    return lambda _: x

reveal_type(f)          # 推断为: Callable[[T], Callable[[int], T]]
reveal_type(g)          # 推断为: Callable[[T], Callable[[int], T]]
reveal_type(f(1))       # 推断为: Callable[[int], int]
reveal_type(g(1))       # 推断为: Callable[[int], int]

方案3:使用泛型类装饰器

通过定义泛型类作为装饰器,让mypy更清晰地追踪泛型变量的生命周期:

from typing import TypeVar, ParamSpec, Concatenate
from typing import Callable as Fn, reveal_type, Generic

P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")


class curry(Generic[T, P, R]):
    def __call__(self, f: Fn[Concatenate[T, P], R]) -> Fn[[T], Fn[P, R]]:
        def outer(x: T) -> Fn[P, R]:
            def inner(*args: P.args, **kwargs: P.kwargs) -> R:
                return f(x, *args, **kwargs)
            return inner
        return outer

curry = curry()


@curry
def f(x: T, y: int) -> T:
    """Test function."""
    return x


def g(x: T, /) -> Fn[[int], T]:
    """Test function."""
    return lambda _: x

reveal_type(f)          # 推断为: Callable[[T], Callable[[int], T]]
reveal_type(g)          # 推断为: Callable[[T], Callable[[int], T]]
reveal_type(f(1))       # 推断为: Callable[[int], int]
reveal_type(g(1))       # 推断为: Callable[[int], int]

以上三种方案均可解决mypy报错问题,可根据项目的Python版本和代码风格选择合适的方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 02:16:09