如何为继承类的构造函数动态设置类型提示?
问题描述
使用@dataclasses.dataclass装饰的类,其构造函数在Pyright等LSP工具中能自动提示类中定义的字段参数:
import dataclasses @dataclasses.dataclass class A: a: int b: float A(<cursor location>) # 此处能自动提示a和b参数
现在需要为自定义父类实现相同效果,让子类继承后,构造函数也能自动提示类中定义的字段参数。现有示例代码如下:
class Parent: def __init__(self, **kvargs): # 已实现参数校验等逻辑 for k, v in kvargs.items(): setattr(self, k, v) def __init_subclass(cls) -> None: # 如何基于子类的类型注解,为A.__init__添加a和b的类型提示? print(get_type_hints(cls)) setattr(cls, "__init__", # 如何从get_type_hints生成带类型注解的__init__函数签名? def __init__(a: int, b: int): super().__init__(a=a, b=b) ) class A(Parent): a: int b: float A(<cursor location>) # 希望此处能自动提示a和b参数
核心需求:已实现接收**kvargs的自定义__init__并完成参数校验,现在要为其添加类型注解信息,让LSP工具能识别并提供自动补全。
实现方案
要让LSP工具识别动态生成的构造函数参数,关键是给子类的__init__函数设置正确的__annotations__属性,同时生成符合类型提示要求的函数签名。可以通过以下步骤实现:
- 提取子类类型注解:在
__init_subclass__中用get_type_hints(cls)获取子类定义的所有字段及其类型。 - 动态生成带注解的__init__:基于提取的类型注解,构造接收对应参数的
__init__函数,内部调用父类__init__传递参数。 - 配置注解与签名:确保生成的
__init__的__annotations__(及可选的__signature__)符合LSP识别要求。
示例代码如下:
import inspect from typing import get_type_hints class Parent: def __init__(self, **kvargs): # 已实现的参数校验逻辑 for k, v in kvargs.items(): setattr(self, k, v) def __init_subclass(cls) -> None: # 获取子类的类型注解 hints = get_type_hints(cls) if not hints: return # 动态生成__init__函数的源代码 param_defs = ", ".join([f"{name}: {hint.__name__}" for name, hint in hints.items()]) init_code = f""" def __init__(self, {param_defs}): super().__init__({', '.join([f'{name}={name}' for name in hints.keys()])}) """ # 执行代码生成函数 local_vars = {} exec(init_code, globals(), local_vars) new_init = local_vars["__init__"] # 设置函数注解 new_init.__annotations__ = hints.copy() new_init.__annotations__["self"] = cls # 为LSP工具设置函数签名(部分工具依赖此属性) sig = inspect.signature(new_init) new_init.__signature__ = sig # 替换子类的__init__ setattr(cls, "__init__", new_init) class A(Parent): a: int b: float # 此时在Pyright中输入A( 会自动提示a和b参数 A(a=1, b=2.5)
关键说明
- 动态函数生成:通过
exec生成匹配子类字段的__init__函数,确保参数名和类型注解与子类定义完全一致。 - 注解配置:将子类的类型注解直接赋值给新
__init__的__annotations__,LSP工具会读取该属性提供自动补全。 - 签名优化:设置
__signature__属性能让部分LSP工具更精准识别参数,提升补全体验。
如果不想用exec,也可以通过functools.wraps和inspect.Signature手动构造函数签名,但exec的方式更直观,能直接生成符合语法规范的函数定义。
内容的提问来源于stack exchange,提问作者KamilCuk
相关产品推荐
相关产品推荐

