如何避免泛型基类中重复指定配置模型以实现Pylance正确类型检查?
避免泛型基类中重复指定Pydantic模型
问题
需要实现多个继承抽象基类的子类,每个子类对应专属的Pydantic BaseModel配置模型。初始写法中Pylance无法正确识别self.cfg的类型;改成泛型基类后解决了类型检查问题,但子类必须在继承声明和类变量中重复指定模型,希望避免这种重复。
初始代码(类型检查失效)
from abc import ABC from typing import TypeVar from pydantic import BaseModel _M = TypeVar("_M", bound=BaseModel) class A(BaseModel): a = 1 b = 2 c = 3 class Base(ABC): cfg_model: type[_M] def __init__(self): self.cfg = self.cfg_model() class ChildA(Base): cfg_model = A def run(self): self.cfg_model # 类型为type[_M] self.cfg # 类型为_M # Pylance无法识别self.cfg的属性 self.cfg.a self.cfg.b self.cfg.c
泛型改进版(存在重复指定)
from abc import ABC from typing import TypeVar, Generic from pydantic import BaseModel _M = TypeVar("_M", bound=BaseModel) class A(BaseModel): a = 1 b = 2 c = 3 class Base(ABC, Generic[_M]): cfg_model: type[_M] def __init__(self): self.cfg = self.cfg_model() class ChildA(Base[A]): cfg_model = A def run(self): self.cfg_model # 类型为type[A] self.cfg # 类型为A # 类型检查正常工作 self.cfg.a self.cfg.b self.cfg.c
解决方案
方案1:元类自动推导泛型参数
通过元类在子类创建时,自动根据cfg_model的值替换基类为对应的泛型实例,无需手动指定Base[A]:
from abc import ABC from typing import TypeVar, Generic, Type from pydantic import BaseModel _M = TypeVar("_M", bound=BaseModel) class BaseMeta(type(ABC), type(Generic)): def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, object]) -> type: cfg_model = namespace.get("cfg_model") if cfg_model is not None and issubclass(cfg_model, BaseModel): # 替换基类中的Base为Base[cfg_model] new_bases = [] for base in bases: if base is Base: new_bases.append(Base[cfg_model]) else: new_bases.append(base) bases = tuple(new_bases) return super().__new__(cls, name, bases, namespace) class Base(ABC, Generic[_M], metaclass=BaseMeta): cfg_model: Type[_M] def __init__(self): self.cfg = self.cfg_model() class A(BaseModel): a = 1 b = 2 c = 3 class ChildA(Base): cfg_model = A def run(self): # Pylance正确识别self.cfg为A类型 print(self.cfg.a, self.cfg.b, self.cfg.c)
方案2:类装饰器绑定模型
使用装饰器为子类自动绑定对应的泛型模型,无需手动设置cfg_model和泛型基类:
from abc import ABC from typing import TypeVar, Generic, Type from pydantic import BaseModel _M = TypeVar("_M", bound=BaseModel) class Base(ABC, Generic[_M]): cfg_model: Type[_M] def __init__(self): self.cfg = self.cfg_model() def configure_with_model(model: Type[_M]): def decorator(cls: Type[Base]) -> Type[Base[_M]]: class ConfiguredClass(cls, Base[model]): cfg_model = model # 保留原类的名称和路径 ConfiguredClass.__name__ = cls.__name__ ConfiguredClass.__qualname__ = cls.__qualname__ return ConfiguredClass return decorator class A(BaseModel): a = 1 b = 2 c = 3 @configure_with_model(A) class ChildA(Base): def run(self): print(self.cfg.a, self.cfg.b, self.cfg.c)
说明
- 元类方案:对原有代码侵入性低,子类写法接近初始版本,仅需设置
cfg_model即可。 - 装饰器方案:更灵活,支持动态切换模型,但需要修改子类的定义方式。
内容的提问来源于stack exchange,提问作者HYBRID BEING
相关产品推荐
相关产品推荐

