Python基于继承实现带Callable类型检查的接口问题
解决Python中Callable风格接口的类型检查问题
问题
需要定义一个可继承的接口,让子类实现的__call__方法能被类型检查工具识别,希望类可以像带类型注解的Callable一样使用,也接受直接继承Callable的方案,但当前代码无法对__call__方法进行有效的类型校验。
问题根源
原代码存在以下关键问题:
- 接口
FunctionInterface未定义抽象的__call__方法,导致类型检查器无法将泛型参数PARAMS、VALUE与__call__的签名关联 - 子类
FunctionBase继承时未指定具体的泛型参数,丢失了类型约束 - 实例注解的泛型参数与实际类的
__call__签名不匹配时,类型检查器无法识别错误
修正方案
方案一:基于ABC+泛型的接口约束
通过在抽象基类中定义绑定泛型参数的抽象__call__方法,明确接口的类型约束:
from abc import ABC, abstractmethod from typing import Generic, TypeVar from typing_extensions import ParamSpec PARAMS = ParamSpec("PARAMS") VALUE = TypeVar("VALUE") class FunctionInterface(ABC, Generic[PARAMS, VALUE]): @abstractmethod def __call__(self, *args: PARAMS.args, **kwargs: PARAMS.kwargs) -> VALUE: """抽象调用方法,绑定泛型参数的参数和返回值类型""" def __init__(self, *args, **kwargs) -> None: ... class FunctionBase(FunctionInterface[[int], int]): def __init__(self, *args, **kwargs) -> None: print("FunctionBase.__init__") def __call__(self, whatever: int) -> int: return whatever # 正确的实例注解,与FunctionBase的__call__签名匹配 impl: FunctionInterface[[int], int] = FunctionBase() ret = impl(100) # 类型检查通过 # 以下代码会触发类型检查错误: # impl: FunctionInterface[[str, Any], str] = FunctionBase() # 泛型参数不匹配 # ret = impl("100") # 参数类型不符合int要求
方案二:直接继承Callable
如果允许接口继承Callable,可以利用Callable的泛型特性直接实现类型约束,代码更简洁:
from abc import ABC, abstractmethod from typing import Callable, Generic, TypeVar from typing_extensions import ParamSpec PARAMS = ParamSpec("PARAMS") VALUE = TypeVar("VALUE") class FunctionInterface(ABC, Callable[PARAMS, VALUE], Generic[PARAMS, VALUE]): def __init__(self, *args, **kwargs) -> None: ... class FunctionBase(FunctionInterface[[int], int]): def __init__(self, *args, **kwargs) -> None: print("FunctionBase.__init__") def __call__(self, whatever: int) -> int: return whatever impl: FunctionInterface[[int], int] = FunctionBase() ret = impl(100) # 类型检查通过 # impl("100") # 参数类型错误,触发类型检查报错
关键说明
- 两种方案的核心都是将
__call__方法的签名与泛型参数PARAMS、VALUE绑定,让类型检查器能校验子类实现的正确性 - 子类继承时必须指定具体的泛型参数(如
FunctionInterface[[int], int]),明确__call__的参数和返回值类型 - 实例注解需与子类实际的
__call__签名一致,否则类型检查器会及时报错,实现预期的类型校验效果
内容的提问来源于stack exchange,提问作者Wiktor Kaczor
相关产品推荐
相关产品推荐

