如何为泛型基类的任意子类变量添加类型注解?
问题场景
你有一对关联的抽象基类TobeProcessed和泛型Processor,子类化后在实现WrapperClass时遇到了mypy严格模式下的类型不兼容错误:
- 直接用
Processor作为类型标注会触发--disallow-any-generics报错 - 改用
Processor[TobeProcessed]时,ProcessorConcrete实例无法赋值给该类型,报错:Argument "processor" to "WrapperClass" has incompatible type "ProcessorConcrete"; expected "Processor[TobeProcessed]"
原代码示例
from abc import ABC, abstractmethod from typing import Generic, TypeVar class TobeProcessed(ABC): pass TobeProcessedType = TypeVar("TobeProcessedType", bound=TobeProcessed) class Processor(ABC, Generic[TobeProcessedType]): @abstractmethod def process(self, to_be_processed: TobeProcessedType) -> None: pass class TobeProcessedConcrete(TobeProcessed): pass class ProcessorConcrete(Processor[TobeProcessedConcrete]): def process(self, to_be_processed: TobeProcessedConcrete) -> None: return None class WrapperClass: processor: Processor[TobeProcessed] def __init__(self, processor: Processor[TobeProcessed]) -> None: self.processor = processor processor = ProcessorConcrete() wrapper = WrapperClass(processor=processor) # mypy报错
问题原因
Python泛型默认是**不变(invariant)**的:即使TobeProcessedConcrete是TobeProcessed的子类,Processor[TobeProcessedConcrete]也不会被视为Processor[TobeProcessed]的子类。这是类型安全的要求——Processor[TobeProcessed]的process方法可以接受任意TobeProcessed实例,但ProcessorConcrete的process只能处理TobeProcessedConcrete,强行赋值会导致潜在的类型错误。
解决方案
方案1:将WrapperClass定义为泛型类(推荐,类型完全安全)
让WrapperClass适配任意具体的Processor[T]类型,保持类型精确性:
from abc import ABC, abstractmethod from typing import Generic, TypeVar class TobeProcessed(ABC): pass TobeProcessedType = TypeVar("TobeProcessedType", bound=TobeProcessed) class Processor(ABC, Generic[TobeProcessedType]): @abstractmethod def process(self, to_be_processed: TobeProcessedType) -> None: pass class TobeProcessedConcrete(TobeProcessed): pass class ProcessorConcrete(Processor[TobeProcessedConcrete]): def process(self, to_be_processed: TobeProcessedConcrete) -> None: return None # 定义泛型WrapperClass,适配任意TobeProcessed子类对应的Processor WrapperT = TypeVar("WrapperT", bound=TobeProcessed) class WrapperClass(Generic[WrapperT]): processor: Processor[WrapperT] def __init__(self, processor: Processor[WrapperT]) -> None: self.processor = processor processor = ProcessorConcrete() # mypy可自动推断泛型参数,无需显式指定 wrapper = WrapperClass(processor=processor)
此方案完全符合严格类型检查要求,且保留了所有类型信息,后续调用processor.process时能保证类型安全。
方案2:定义非泛型基类(适用于无需调用process方法的场景)
如果WrapperClass仅需持有Processor实例,不需要调用其process方法,可以给Processor添加一个非泛型的抽象基类:
from abc import ABC, abstractmethod from typing import Generic, TypeVar class BaseProcessor(ABC): pass class TobeProcessed(ABC): pass TobeProcessedType = TypeVar("TobeProcessedType", bound=TobeProcessed) class Processor(BaseProcessor, ABC, Generic[TobeProcessedType]): @abstractmethod def process(self, to_be_processed: TobeProcessedType) -> None: pass class TobeProcessedConcrete(TobeProcessed): pass class ProcessorConcrete(Processor[TobeProcessedConcrete]): def process(self, to_be_processed: TobeProcessedConcrete) -> None: return None class WrapperClass: processor: BaseProcessor def __init__(self, processor: BaseProcessor) -> None: self.processor = processor processor = ProcessorConcrete() wrapper = WrapperClass(processor=processor)
此方案避免了泛型类型不兼容问题,但代价是WrapperClass无法直接调用processor.process(因为BaseProcessor未定义该方法)。
方案3:使用协变类型变量(需谨慎,仅适用于特定场景)
如果WrapperClass调用process时只会传入TobeProcessedConcrete或其子类,可以将Processor的类型变量设为协变:
from abc import ABC, abstractmethod from typing import Generic, TypeVar class TobeProcessed(ABC): pass # 添加covariant=True使类型变量协变 TobeProcessedType = TypeVar("TobeProcessedType", bound=TobeProcessed, covariant=True) class Processor(ABC, Generic[TobeProcessedType]): @abstractmethod def process(self, to_be_processed: TobeProcessedType) -> None: pass class TobeProcessedConcrete(TobeProcessed): pass class ProcessorConcrete(Processor[TobeProcessedConcrete]): def process(self, to_be_processed: TobeProcessedConcrete) -> None: return None class WrapperClass: processor: Processor[TobeProcessed] def __init__(self, processor: Processor[TobeProcessed]) -> None: self.processor = processor processor = ProcessorConcrete() wrapper = WrapperClass(processor=processor)
注意:协变在这里属于“灰色地带”——从类型理论上讲,输入参数使用协变是不安全的(可能导致传入超类实例时出错),仅当你能保证WrapperClass永远不会给process传入非TobeProcessedConcrete的TobeProcessed实例时,才建议使用此方案。
内容的提问来源于stack exchange,提问作者MaartenB

