如何让Python子类__call__继承run方法的类型提示?
问题:让子类
__call__继承run方法的类型提示 需求概述
- 在
ParentClass的子类中实现run方法,子类实例调用时(即触发__call__)自动执行run,同时希望run的参数、返回值类型提示能直接应用到__call__的调用处 - 子类仅需定义
run方法,无需手动实现__call__,也不想在子类中指定元类 - 要在
__call__执行前后插入统一的包装逻辑
期望的子类定义与使用方式
子类定义示例
from abc import abstractmethod class ParentClass: # 父类承载复杂实现,让子类保持简洁 @abstractmethod def run(self): raise NotImplementedError() class MyClass(ParentClass): def run(self, a: int = 5) -> int: print("my", a) return 1 class MyClass2(ParentClass): def run(self, b: str = "", c: bool = False) -> int: print("my2", b, c) return 2
调用示例
my = MyClass() my(a=6) # 打印`my 6`并返回`1`,期望此处能看到`a`的类型提示 my2 = MyClass2() my2(b="example", c=True) # 打印`my2 example True`并返回`2`,期望此处能看到`b`、`c`的类型提示
已尝试的方案(存在类型提示丢失问题)
from abc import abstractmethod from functools import wraps from typing import Callable, TypeVar, ParamSpec, Self, Any, cast from __future__ import annotations P = ParamSpec("P") T = TypeVar("T") class ParentMeta(type): def __new__(cls: Self, name: str, bases: tuple, namespace: dict[str, Any]) -> ParentMeta: def wrapper(func: Callable[P, T]) -> Callable[P, T]: @wraps(func) def inner(self, *args: P.args, **kwargs: P.kwargs) -> T: print("--pre--") ret = func(self, *args, **kwargs) print("--post--") return ret return inner if "run" in namespace: namespace["__call__"] = wrapper(namespace["run"]) del namespace["run"] return cast(ParentMeta, super().__new__(cls, name, bases, namespace)) class ParentClass(object, metaclass=ParentMeta): @abstractmethod def run(self): raise NotImplementedError() class MyClass(ParentClass): def run(self, a: int = 5) -> int: print("my", a) return 1 my = MyClass() my(a=6) # 问题:IDE(VSCode)中输入`my(`时,无法看到`a`的参数提示
对比方案(手动定义__call__能正常获取提示,但不符合需求)
from abc import abstractmethod from functools import wraps from typing import Callable, TypeVar, ParamSpec, Self, Any, cast from __future__ import annotations P = ParamSpec("P") T = TypeVar("T") class ParentMeta(type): def __new__(cls: Self, name: str, bases: tuple, namespace: dict[str, Any]) -> ParentMeta: def wrapper(func: Callable[P, T]) -> Callable[P, T]: @wraps(func) def inner(self, *args: P.args, **kwargs: P.kwargs) -> T: print("--pre--") ret = func(self, *args, **kwargs) print("--post--") return ret return inner if "__call__" in namespace: namespace["__call__"] = wrapper(namespace["__call__"]) return cast(ParentMeta, super().__new__(cls, name, bases, namespace)) class ParentClass(object, metaclass=ParentMeta): @abstractmethod def __call__(self): raise NotImplementedError() class MyClass(ParentClass): def __call__(self, a: int = 5) -> int: print("my", a) return 1 my = MyClass() my(a=6) # 输入`my(`时能正常看到`(a: int = 5) -> int`的提示
解决方案:利用泛型+ParamSpec实现类型绑定
通过泛型父类将__call__的签名与run方法动态绑定,同时保留元类的包装逻辑,让类型检查器能自动识别__call__的参数提示。
from abc import abstractmethod from functools import wraps from typing import Callable, TypeVar, ParamSpec, Generic, Any, cast from __future__ import annotations P = ParamSpec("P") T = TypeVar("T") class ParentMeta(type): def __new__(cls, name: str, bases: tuple, namespace: dict[str, Any]) -> ParentMeta: def wrapper(func: Callable[P, T]) -> Callable[P, T]: @wraps(func) def inner(self, *args: P.args, **kwargs: P.kwargs) -> T: print("--pre--") ret = func(self, *args, **kwargs) print("--post--") return ret return inner # 处理run方法:包装后替换为__call__,同时保留原run供类型检查器识别 if "run" in namespace: run_func = namespace["run"] namespace["__call__"] = wrapper(run_func) return cast(ParentMeta, super().__new__(cls, name, bases, namespace)) # 泛型父类,将__call__的签名与run的参数(P)、返回值(T)绑定 class ParentClass(Generic[P, T], metaclass=ParentMeta): @abstractmethod def run(self, *args: P.args, **kwargs: P.kwargs) -> T: raise NotImplementedError() # 显式定义__call__的类型提示,指向run的签名 def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: return self.run(*args, **kwargs) # 子类实现 class MyClass(ParentClass[P, int]): def run(self, a: int = 5) -> int: print("my", a) return 1 class MyClass2(ParentClass[P, int]): def run(self, b: str = "", c: bool = False) -> int: print("my2", b, c) return 2 # 使用验证 my = MyClass() my(a=6) # IDE中能正常看到a的参数提示和返回值类型 my2 = MyClass2() my2(b="example", c=True) # IDE中能看到b、c的参数提示
方案说明
- 泛型绑定:通过
Generic[P, T]让父类的__call__签名与run方法的参数、返回值动态绑定,类型检查器能自动推导子类run的签名到__call__上。 - 元类逻辑:元类在创建子类时,将
run方法包装后替换为__call__,保留原run方法避免类型检查器报错。 - 类型提示兼容:父类中显式定义
__call__的类型提示,确保IDE能识别参数信息。
备选方案:使用Annotated简化泛型写法
如果觉得泛型写法繁琐,可以用Annotated配合类型别名简化:
from abc import abstractmethod from functools import wraps from typing import Callable, TypeVar, ParamSpec, Generic, Annotated, Any, cast from __future__ import annotations P = ParamSpec("P") T = TypeVar("T") RunSignature = Annotated[Callable[P, T], "run method signature"] class ParentMeta(type): def __new__(cls, name: str, bases: tuple, namespace: dict[str, Any]) -> ParentMeta: def wrapper(func: Callable[P, T]) -> Callable[P, T]: @wraps(func) def inner(self, *args: P.args, **kwargs: P.kwargs) -> T: print("--pre--") ret = func(self, *args, **kwargs) print("--post--") return ret return inner if "run" in namespace: namespace["__call__"] = wrapper(namespace["run"]) return cast(ParentMeta, super().__new__(cls, name, bases, namespace)) class ParentClass(Generic[P, T], metaclass=ParentMeta): @abstractmethod def run(self, *args: P.args, **kwargs: P.kwargs) -> T: raise NotImplementedError() __call__: RunSignature class MyClass(ParentClass[P, int]): def run(self, a: int = 5) -> int: print("my", a) return 1 my = MyClass() my(a=6) # 同样能获取类型提示
内容的提问来源于stack exchange,提问作者gkrupp
相关产品推荐
相关产品推荐

