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

如何让mypy识别lazy_decorator对函数参数类型的扩展,实现LazyOp参数的透明支持?

如何让mypy识别lazy_decorator对函数参数类型的扩展,实现LazyOp参数的透明支持?

解决方案:通过类型别名与参数规范映射,让mypy自动兼容LazyOp参数

要实现你的需求,我们需要修改装饰器的类型注解,让mypy理解:被装饰函数的每个参数既可以接受原始类型,也可以接受对应的LazyOp类型。核心思路是用TypeVar、ParamSpec和类型别名来动态扩展参数类型范围,同时保留原函数的类型注解可读性。

以下是修改后的完整代码,附带详细说明:

from functools import wraps
from typing import Any, Callable, Generic, ParamSpec, TypeVar, Union, TypeAlias

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

# 定义辅助类型:表示「原始类型T」或「对应的LazyOp[T]」
LazyOr: TypeAlias = Union[T, "LazyOp[T]"]

class LazyOp(Generic[T]):
    def __init__(self, func: Callable[..., T], args: tuple[Any, ...], kwargs: dict[str, Any]):
        self.func = func
        self.args = args
        self.kwargs = kwargs

    def __call__(self) -> T:
        args = [a() if isinstance(a, LazyOp) else a for a in self.args]
        kwargs = {k: v() if isinstance(v, LazyOp) else v for k, v in self.kwargs.items()}
        return self.func(*args, **kwargs)

# 自定义ParamSpec,将原函数的所有参数类型转换为LazyOr包裹的版本
class LazyParamSpec(ParamSpec):
    args: tuple[LazyOr[t] for t in P.args]
    kwargs: dict[str, LazyOr[t] for t in P.kwargs.values()]

def lazy_decorator(func: Callable[P, T]) -> Callable[LazyParamSpec[P], LazyOp[T]]:
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> LazyOp[T]:
        # 运行时逻辑完全不变,仅修改类型注解
        return LazyOp(func, args, kwargs)

    return wrapper

@lazy_decorator
def lazy_sum(a: int, b: int) -> int:
    return a + b

lazy_result = lazy_sum(1, 2)
# 现在mypy不会报错:LazyOp[int] 被自动识别为合法参数
lazy_result = lazy_sum(lazy_result, 4)
result = lazy_result()
print(result)  # 输出7

关键设计细节(满足你的所有需求):

  1. 保留原始类型注解
    被装饰的函数lazy_sum仍然显示接受int类型参数,开发者看到的还是熟悉的原始签名,@wraps也保留了原函数的元数据。

  2. 自动兼容LazyOp参数
    通过LazyParamSpec,mypy会自动将原函数的每个参数类型扩展为「原始类型 + LazyOp类型」的联合,无需手动修改每个函数的签名。

  3. 无需手动调整
    所有类型转换逻辑都封装在装饰器中,被装饰函数完全不需要修改自己的类型注解,直接使用即可。

  4. 无缝集成

    • 不依赖任何外部mypy插件,仅使用Python标准库的类型注解特性
    • 运行时代码完全不变,没有任何性能损失
    • 无需大幅重构现有代码,仅替换装饰器的类型注解部分即可

兼容旧版mypy的备选方案

如果你使用的mypy版本不支持自定义ParamSpec子类(低于0.1000版本),可以改用TypeVarTuple和Unpack实现相同效果:

from functools import wraps
from typing import Any, Callable, Generic, TypeVar, Union, TypeAlias, TypeVarTuple, Unpack

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

LazyOr: TypeAlias = Union[T, "LazyOp[T]"]

class LazyOp(Generic[T]):
    # 保持原实现不变
    def __init__(self, func: Callable[..., T], args: tuple[Any, ...], kwargs: dict[str, Any]):
        self.func = func
        self.args = args
        self.kwargs = kwargs

    def __call__(self) -> T:
        args = [a() if isinstance(a, LazyOp) else a for a in self.args]
        kwargs = {k: v() if isinstance(v, LazyOp) else v for k, v in self.kwargs.items()}
        return self.func(*args, **kwargs)

def lazy_decorator(func: Callable[Unpack[Ts], T]) -> Callable[tuple[LazyOr[Unpack[Ts]]], LazyOp[T]]:
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> LazyOp[T]:
        return LazyOp(func, args, kwargs)

    return wrapper

# 用法完全一致
@lazy_decorator
def lazy_sum(a: int, b: int) -> int:
    return a + b

备注:内容来源于stack exchange,提问作者Phinnik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 12:25:28