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

78 views Asked by At

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?

1

There are 1 answers

1
Paweł Rubin On BEST ANSWER

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)