使用装饰器创建子类并保留类型提示的Python技术问题(3.10+)
解决Python 3.10+装饰器创建子类时的类型提示问题
你的问题核心在于装饰器的类型注解没有准确描述返回类的实际类型:
- 第一个装饰器返回
Type[T],类型检查器认为返回的是原父类,因此只能识别父类属性,无法感知新增的方法; - 第二个装饰器无类型注解,类型检查器无法推断原父类的属性,只能识别新增方法。
解决方案:使用Protocol定义新增方法,结合类型交集实现双向类型提示
通过typing.Protocol定义新增方法的契约,再让装饰器返回原类类型与Protocol类型的交集,这样类型检查器就能同时识别父类属性和新增方法。
from pydantic import BaseModel import typing as t # 定义包含新增方法的协议 class HasGetTemplateObj(t.Protocol): def get_template_obj(self) -> str: ... T = t.TypeVar('T') def wrapper(aclass: t.Type[T]) -> t.Type[T] & t.Type[HasGetTemplateObj]: class SqlModel(aclass): def get_template_obj(self) -> str: return 'example' # 用cast告诉类型检查器返回类的实际类型 return t.cast(t.Type[T] & t.Type[HasGetTemplateObj], SqlModel) @wrapper class MyTestModel(BaseModel): kingappid: int = 17 exampleattr: str = 'example' # 现在以下操作都能获得正确的类型提示 MyTestModel().kingappid # 识别为int MyTestModel().exampleattr # 识别为str MyTestModel().get_template_obj() # 识别为str
原理说明
- Protocol:定义了新增方法的类型契约,告诉类型检查器只要类实现了
get_template_obj方法,就符合该协议; - 类型交集:
Type[T] & Type[HasGetTemplateObj]表示返回的类同时属于原父类T和协议HasGetTemplateObj,类型检查器会合并两者的成员信息; - cast:由于Python运行时无法直接识别类型交集,用
cast向类型检查器声明返回类的实际类型,避免类型报错。
替代方案(Python 3.11+)
如果使用Python 3.11及以上版本,可以用typing.Self简化,但上述方案在3.10+也能完美运行:
# Python 3.11+ 可选写法 def wrapper(aclass: t.Type[T]) -> t.Type[T]: class SqlModel(aclass): def get_template_obj(self: t.Self) -> str: return 'example' return t.cast(t.Type[T], SqlModel)
不过这种写法需要类型检查器支持Self的跨类推断,不如Protocol方案稳定可靠。
内容的提问来源于stack exchange,提问作者theo2021
相关产品推荐
相关产品推荐

