Python's type hinting system is there for a static type checker to validate your code and T
is just a placeholder for the type system, like a slot in a template language. It can't be used as an indirect reference to a specific type.
You need to subclass your generic type if you want to produce a concrete implementation. And because Gender
is a class and not an instance, you'd need to tell the type system how you plan to use a Type[T]
somewhere, too.
Because you also want to be able to use T
as an Enum()
(calling it with EnumSubclass(int(character))
), I'd also bind the typevar; that way the type checker will understand that all concrete forms of Type[T]
are callable and will produce individual T
instances, but also that those T
instances will always have a .value
attribute:
from typing import ClassVar, List, Union, Type, TypeVar, Generic
from enum import IntEnum
T = TypeVar('T', bound=IntEnum) # only IntEnum subclasses
class EnumAggregate(Generic[T]):
# Concrete implementations can reference `enum` *on the class itself*,
# which will be an IntEnum subclass.
enum: ClassVar[Type[T]]
def __init__(self, value: Union[int, str, List[T]]) -> None:
if not value:
raise ValueError('Parameter "value" cannot be empty!')
if isinstance(value, list):
self._value = ''.join([str(x.value) for x in value])
else:
self._value = str(value)
def __contains__(self, item: T) -> bool:
return item in self.to_list
@property
def to_list(self) -> List[T]:
# the concrete implementation needs to use self.enum here
return [self.enum(int(character)) for character in self._value]
@property
def value(self) -> str:
return self._value
@classmethod
def all(cls) -> str:
# the concrete implementation needs to reference cls.enum here
return ''.join([str(x.value) for x in cls.enum])
With the above generic class you can now create a concrete implementation, using your Gender
IntEnum
fitted into the T
slot and as a class attribute:
class Gender(IntEnum):
MALE = 1
FEMALE = 2
DIVERS = 3
class Genders(EnumAggregate[Gender]):
enum = Gender
To be able to access the IntEnum
subclass as a class attribute, we needed to use typing.ClassVar[]
; otherwise the type checker has to assume the attribute is only available on instances.
And because the Gender
IntEnum
subclass is itself a class, we need to tell the type checker about that too, hence the use of typing.Type[]
.
Now the Gender
concrete subclass works; the use of EnumAggregate[Gender]
as a base class tells the type checker to substitute T
for Gender
everywhere, and because the implementation uses enum = Gender
, the type checker sees that this is indeed correctly satisfied and the code passes all checks:
$ bin/mypy so65064844.py
Success: no issues found in 1 source file
and you can call Genders.all()
to produce a string:
>>> Genders.all()
'123'
Note that I'd not store the enum values as strings, but rather as integers. There is little value in converting it back and forth here, and you are limiting yourself to enums with values between 0 and 9 (single digits).