pythongenericsmypypython-typingabstract-base-class

How to type-hint a variable whose type is any subclass of a generic base class?


I have two abstract base classes that are linked, and should be subclassed together. For the sake of a minimal example, let's say its some class TobeProcessed, and a another class Processor that performs some processing on instances of the TobeProcessed class. I made the Processor Generic with the type of the TobeProcessed class as type-argument.

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

Now I have some concrete implementations of both classes:

class TobeProcessedConcrete(TobeProcessed):
    pass


class ProcessorConcrete(Processor[TobeProcessedConcrete]):
    def process(self, to_be_processed: TobeProcessedConcrete) -> None:
        return None

Finally, I have a "wrapper" class which has an attribute processor which is an instance of any subclass of the Processor class.

class WrapperClass:
    processor: Processor

    def __init__(self, processor: Processor) -> None:
        self.processor = processor


processor = ProcessorConcrete()
wrapper = WrapperClass(processor=processor)

If I check this with mypy with --disallow-any-generics (or --strict), I get two errors for WrapperClass because I omitted the type parameter for Processor, which makes sense. However, if I replace Processor with Processor[TobeProcessed], I get an error for the line wrapper = WrapperClass(processor=processor):

Argument "processor" to "WrapperClass" has incompatible type "ProcessorConcrete"; expected "Processor[TobeProcessed]".

Is there a way to do this without errors, and without making mypy less strict?


Solution

  • It should be Processor[TobeProcessedType] instead.

    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(Generic[TobeProcessedType]):
        processor: Processor[TobeProcessedType]
    
        def __init__(self, processor: Processor[TobeProcessedType]) -> None:
            self.processor = processor
    
    
    processor = ProcessorConcrete()
    wrapper = WrapperClass(processor=processor)