I have two abstract classes, AbstractA and AbstractB. AbstractB is generic and its type parameter is bound to AbstractA. AbstractB further has a factory classmethod that returns an instance of one of its subclasses--which one is determined from some input parameter. See below for a minimal example. Note that, after trial and error, I found that I need to add a type hint for B_type in AbstractB.factory().
Minimal example
from __future__ import annotations
from abc import ABC
from typing import Generic, TypeVar, Type, Any
class AbstractA(ABC):
pass
class ConcreteA1(AbstractA):
pass
class ConcreteA2(AbstractA):
pass
ATypeT = TypeVar("ATypeT", bound=AbstractA)
class AbstractB(ABC, Generic[ATypeT]):
@classmethod
def factory(cls, typeB_selector: int) -> AbstractB[Any]: # which type hint here?
B_type: Type[AbstractB[Any]] # which type hint here?
if typeB_selector == 1:
B_type= ConcreteB1
elif typeB_selector == 2:
B_type= ConcreteB2
return B_type()
class ConcreteB1(AbstractB[ConcreteA1]):
pass
class ConcreteB2(AbstractB[ConcreteA2]):
pass
I'm trying to understand what type hints to use for the return value of AbstractB.factory() and B_type. From my (admittedly limited) understanding of generics, I believe the appropriate type should be AbstractB[AbstractA]. However, with strict=true, mypy gives errors for the two B_type=... lines; e.g. for the first one:
Incompatible types in assignment (expression has type "type[ConcreteB1]", variable has type "type[AbstractB[AbstractA]]").
The only way I can avoid errors is by using AbstractB[Any], as in the example. However, this feels wrong to me, since we know that the type parameter is bound to AbstractA. I also tried AbstractB[ATypeT], but that seems wrong as well, and also results in mypy errors.
Questions
- Is my assumption that the correct type hint should be
AbstractB[AbstractA]correct, or am I misunderstanding things? - Am I doing something weird here? According to my understanding of the factory pattern this seems like a valid approach.
Notes
- My actual code properly handles the case when
factory()is called on one of the subclasses (ConcreteB1,ConcreteB2). - There are several similar questions on SO (e.g. 1, 2), but I believe my case is different because (when
factory()is called onAbstractB) there's no way of telling the type checker which subclass will be returned.
Couple of things going on here - first of all some general practices:
Abstract classes should not reference their dependants
To answer your question, yes some of what you're doing here is strange, but the strangest part is that the method on
AbstractBreferences it's dependantsConcreteB1andConcreteB2. An abstract class should represent common behaviour of the children classes - by trying to reference the two concrete classes it's no longer common behaviour really, and somehow requires knowledge of things it will be used for. Hope that makes sense.A solution
Now factory only references the inherited classes. You might be thinking, well wait a minute, I wanted it to return the abstract version. Really though, by having the if statement map
intto class, a union is actually more descriptive of the possible outcomes. In fact you could go one step further and do this:Now it's clear what maps to what, and we also get the specific type depending on the number entered.
A note about abstract factories
You can make abstract classmethods that work as factories, but they can't use classes that inherit from them e.g.:
The above would be fine, as it only references
cls.