Python: Inheriting a class sets the generic type hinting

43 views Asked by At

I have a Car with attribute wheel I have 4 categories of cars:

  • TravelStyle: what is the car purpose
    • Race: All of the attribute of child class of Race should be RaceType
    • Pleasure:
      • All of the attribute of child class of Pleasure should be PleasureType
      • All children class of Pleasure should have method sing

Note: There will always be only these 2 TravelStyles

  • FruitStyle: What is the cars fruit
    • Strawberry: They have specific strawberry attribute
    • Bananas: They have specific banana attribute

Note: In the future, there WILL be the need to add new fruits, like Cherry.

From this I can only create these objects:

  • RaceStrawberryCar: a Strawberry car with Race style
  • PleasureStrawberryCar: a Strawberry car with Pleasure style
  • RaceBananaCar: a Banana car with Race style
  • PleasureBananaCar: a Banana car with Pleasure style

Here is a sample code of an exhaustive way to write these 4 classes:

from dataclasses import dataclass


class PleasureType:
    pass


class RaceType:
    pass


@dataclass
class RaceStrawberryCar:
    wheel: RaceType
    strawberry: RaceType


@dataclass
class PleasureStrawberryCar:
    wheel: PleasureType
    strawberry: PleasureType

    def sing(self, views: int) -> list:
        raise NotImplementedError


@dataclass
class RaceBananaCar:
    wheel: RaceType
    bananas: RaceType


@dataclass
class PleasureBananaCar:
    wheel: PleasureType
    bananas: PleasureType

    def sing(self, views: int) -> list:
        raise NotImplementedError

However, here there are few flaws:

  • Code repetition
  • Hard to extend: Every time we need to add a new fruit ( like Cherry ), we'll need to recreate 2 classes.

My question is therefore: do you have any idea of a design to make this class construction more consistent, and easily extendable ? I set the requirements above.

For now, my first intent was the use of the generic types and inheritance. Here is the sample of code:

from abc import ABC
from dataclasses import dataclass
from typing import Generic, TypeVar


class PleasureType:
    pass


class RaceType:
    pass


T = TypeVar("T")


@dataclass
class Car(Generic[T], ABC):
    wheel: T


@dataclass
class RaceCar(Car[RaceType], ABC):
    pass


@dataclass
class PleasureCar(Car[PleasureType], ABC):
    def sing(self, views: int) -> list:
        raise NotImplementedError


@dataclass
class StrawberryCar(Car[T], ABC):
    strawberries: T


@dataclass
class RaceStrawberryCar(StrawberryCar[RaceType], RaceCar):
    pass


@dataclass
class PleasureStrawberryCar(StrawberryCar[PleasureType], PleasureCar):
    pass


@dataclass
class BananaCar(Car[T], ABC):
    bananas: T


@dataclass
class RaceBananaCar(BananaCar[RaceType], RaceCar):
    pass


@dataclass
class PleasureBananaCar(BananaCar[PleasureType], PleasureCar):
    pass

What I don't like about this approach, is that to set a car of Race type, we need to define it 2 times:

  • Defining the generic type of the base Fruitcar
  • Defining the inheritance from a Race car

I.e. , in the statement class PleasureBananaCar(BananaCar[PleasureType], PleasureCar):, we define it 2 times:

  • By setting the type of BananaCar to PleasureType
  • By stating that the class inherits from PleasureCar

Doing this way, creating this class is totally acceptable:

@dataclass
class HybridBananaCar(BananaCar[PleasureType], RaceCar):
    pass

And I don't want to be able to do that.

My first idea to overcome this, was to try to define the generic type when inheriting from a class, i.e.:

  • If a class inherits from RaceCar, then the generic type is set to RaceType
  • If a class inherits from PleasureCar, then the generic type is set to PleasureType

I tried this, but it obviously doesn't work:

from abc import ABC
from dataclasses import dataclass
from typing import Generic, TypeVar


class PleasureType:
    pass


class RaceType:
    pass


T = TypeVar("T")


@dataclass
class Car(Generic[T], ABC):
    wheel: T


@dataclass
class RaceCar(ABC):
    T = RaceType
    pass


@dataclass
class PleasureCar(ABC):
    T = PleasureType

    def sing(self, views: int) -> list:
        raise NotImplementedError


@dataclass
class StrawberryCar(Car[T], ABC):
    strawberries: list[T]


@dataclass
class RaceStrawberryCar(StrawberryCar, RaceCar):
    pass


@dataclass
class PleasureStrawberryCar(StrawberryCar, PleasureCar):
    pass


@dataclass
class BananaCar(Car[T], ABC):
    bananas: T


@dataclass
class RaceBananaCar(BananaCar, RaceCar):
    pass


@dataclass
class PleasureBananaCar(BananaCar, PleasureCar):
    pass

Any suggestion is welcomed. I repeat, I am not stuck with this class structure, any other suggestion for class structure that fulfills my requirements are welcome.

0

There are 0 answers