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

如何在Python中正确标注泛型可调用对象的类型?

解决命令类与处理器的类型匹配问题

问题场景

我写了一段代码,想实现不同命令类和对应处理器的类型匹配,但遇到了mypy类型检查错误。

原始代码

from collections.abc import Callable, Coroutine
from typing import Any, Generic, TypeVar

CRT = TypeVar("CRT", bound=Any)

class Command(Generic[CRT]): pass

CommandHandler = Callable[[Command[CRT]], Coroutine[Any, Any, CRT]]

class MyCommand(Command[int]): pass

async def my_command_handler(command: MyCommand) -> int:
    return 42

async def process_command(command: Command[CRT], handler: CommandHandler[CRT]) -> CRT:
    return await handler(command)

async def main() -> None:
    my_command = MyCommand()

    await process_command(my_command, my_command_handler)

报错信息

执行await process_command(my_command, my_command_handler)时,mypy抛出错误:

Argument 2 to "process_command" has incompatible type "Callable[[MyCommand], Coroutine[Any, Any, int]]"; expected "Callable[[Command[int]], Coroutine[Any, Any, int]]"
(function) def my_command_handler(command: MyCommand) -> CoroutineType[Any, Any, int]

我知道MyCommand是Command[int]的子类,但类型检查就是不通过。我的目标是让每个命令类定义自己的预期返回结果,同时给process_command添加准确的类型标注,确保处理器只能接收对应命令实例,且返回正确结果。虽然把处理器改成下面的形式能通过检查,但我不想在函数里加额外的类型判断:

async def my_command_handler(command: Command[int]) -> int:
    if not isinstance(command, MyCommand):
        raise TypeError("Expected MyCommand")
    return 42

原因分析

问题出在Callable类型的参数逆变特性:如果类型B是A的子类,那么Callable[[A], ...]可以被视为Callable[[B], ...]的子类,但反过来不行。你的my_command_handler只接受MyCommand类型的参数,而process_command期望的处理器能接受任意Command[int]类型的参数——这就导致了类型不兼容,因为处理器无法处理所有Command[int]的子类。

解决方案

通过给命令类型单独定义协变的TypeVar,让处理器的参数类型和传入的命令类型严格绑定,就能解决这个问题。以下是两种可行的实现方式:

方式一:用两个TypeVar分别绑定命令和返回类型

from collections.abc import Callable, Coroutine
from typing import Any, Generic, TypeVar

# CRT表示处理器的返回类型,CMD表示命令类型(声明为协变)
CRT = TypeVar("CRT", bound=Any)
CMD = TypeVar("CMD", bound="Command[CRT]", covariant=True)

class Command(Generic[CRT]):
    pass

# 让CommandHandler的参数为CMD类型,而非固定的Command[CRT]
CommandHandler = Callable[[CMD], Coroutine[Any, Any, CRT]]

class MyCommand(Command[int]):
    pass

async def my_command_handler(command: MyCommand) -> int:
    return 42

# 修改process_command的类型标注,关联CMD和CRT
async def process_command(command: CMD, handler: CommandHandler[CMD, CRT]) -> CRT:
    return await handler(command)

async def main() -> None:
    my_command = MyCommand()
    await process_command(my_command, my_command_handler)

方式二:直接在process_command中绑定命令子类

from collections.abc import Callable, Coroutine
from typing import Any, Generic, TypeVar

CRT = TypeVar("CRT", bound=Any)
# 声明CMD是Command[CRT]的子类
CMD = TypeVar("CMD", bound=Command[CRT])

class Command(Generic[CRT]):
    pass

class MyCommand(Command[int]):
    pass

async def my_command_handler(command: MyCommand) -> int:
    return 42

# 直接标注处理器接受CMD类型参数
async def process_command(command: CMD, handler: Callable[[CMD], Coroutine[Any, Any, CRT]]) -> CRT:
    return await handler(command)

async def main() -> None:
    my_command = MyCommand()
    await process_command(my_command, my_command_handler)

两种方式都能让mypy通过类型检查,同时不需要在处理器内部添加额外的类型判断,完美实现命令与处理器的类型绑定。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 02:21:16