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

带默认参数的Python typing Protocol类型推导错误求助

为带默认回调参数的函数添加正确的类型标注

我编写了一个执行回调的简单函数,核心逻辑是调用传入的回调函数并返回结果:

def callback(*, arg1: float | None = None) -> float:
    return arg1 or 1.0

def callback2(*, arg1: int | None = None) -> int:
    return arg1 or 1

def func(arg1 = callback, arg2 = None):
    return arg1(arg1=arg2)

func(callback)
func(callback2)

这些回调函数的arg1参数与返回值类型一致,只能是int或float。最初为func添加无默认参数的类型标注时,用typing.Protocol可以正常通过MyPy检查:

from __future__ import annotations

from typing import TypeVar, Protocol

_ResultType = TypeVar("_ResultType", int, float)
_ContraResultType = TypeVar("_ContraResultType", int, float, contravariant=True)
_CoResultType = TypeVar("_CoResultType", int, float, covariant=True)

class MyCallback(Protocol[_ContraResultType, _CoResultType]):
    def __call__(self, *, arg1: _ContraResultType | None) -> _CoResultType: ...

def callback(arg1: float | None = None) -> float:
    return arg1 or 1.0

def callback2(arg1: int | None = None) -> int:
    return arg1 or 1

def func(
    arg1: MyCallback[_ResultType, _ResultType],
    arg2: _ResultType | None = None,
) -> _ResultType:
    return arg1(arg1=arg2)

a: float = func(callback)
b: int = func(callback2)

但为arg1添加默认值callback后:

def func(
    arg1: MyCallback[_ResultType, _ResultType] = callback,
    arg2: _ResultType | None = None,
) -> _ResultType:
    return arg1(arg1=arg2)

MyPy抛出错误:

example.py:19: error: Incompatible default for argument "arg1" (default has type "Callable[[Optional[float]], float]", argument has type "MyCallback[int, int]") [assignment]
example.py:19: note: "MyCallback[int, int].call" has type "Callable[[NamedArg(Optional[int], 'arg1')], int]"
Found 1 error in 1 file (checked 1 source file)

如果把默认值换成callback2,则会出现反向的类型不兼容错误。需要调整类型标注,让带默认参数的版本也能正确推导类型。


解决方案1:使用函数重载(@overload)

问题核心是默认回调只能处理float类型,但_ResultType允许int,导致类型不匹配。通过函数重载为func定义两种场景:一种是使用默认callback(返回float),另一种是传入自定义回调(支持int或float)。

调整后的代码:

from __future__ import annotations

from typing import TypeVar, Protocol, overload

_ResultType = TypeVar("_ResultType", int, float)

class MyCallback(Protocol[_ResultType]):
    def __call__(self, *, arg1: _ResultType | None) -> _ResultType: ...

def callback(*, arg1: float | None = None) -> float:
    return arg1 or 1.0

def callback2(*, arg1: int | None = None) -> int:
    return arg1 or 1

# 重载1:使用默认回调,返回float
@overload
def func(arg1: None = None, arg2: float | None = None) -> float: ...

# 重载2:传入自定义回调,返回对应类型
@overload
def func(arg1: MyCallback[_ResultType], arg2: _ResultType | None = None) -> _ResultType: ...

# 实际实现
def func(
    arg1: MyCallback[_ResultType] | None = None,
    arg2: _ResultType | None = None,
) -> _ResultType:
    actual_arg1 = arg1 if arg1 is not None else callback
    return actual_arg1(arg1=arg2)

# 类型检查全部通过
a: float = func()  # 使用默认callback
b: float = func(callback)
c: int = func(callback2)
d: float = func(callback, 3.5)
e: int = func(callback2, 5)

关键说明:

  • 简化Protocol:回调的参数和返回值类型一致,只用单个_ResultType即可,让Protocol更简洁。
  • 重载区分场景:明确无参/默认参数场景的返回类型,同时保留自定义回调的类型推导能力。
  • 内部处理默认值:将arg1的默认值设为None,在函数内部替换为callback,避免标注冲突。

解决方案2:让默认回调兼容int和float

如果业务逻辑允许默认回调处理int类型,可以修改callback的类型标注,让它接受int | float | None的参数并返回float(int可隐式转为float):

from __future__ import annotations

from typing import TypeVar, Protocol

_ResultType = TypeVar("_ResultType", int, float)

class MyCallback(Protocol[_ResultType]):
    def __call__(self, *, arg1: _ResultType | None) -> _ResultType: ...

def callback(*, arg1: int | float | None = None) -> float:
    return float(arg1) if arg1 is not None else 1.0

def callback2(*, arg1: int | None = None) -> int:
    return arg1 or 1

def func(
    arg1: MyCallback[_ResultType] = callback,
    arg2: _ResultType | None = None,
) -> _ResultType:
    return arg1(arg1=arg2)

# 类型检查全部通过
a: float = func()
b: float = func(callback)
c: int = func(callback2)

这种方案更简洁,但需要确保默认回调的逻辑能兼容int参数的转换。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 09:34:52