Skip to content
Advertisement

Use a different fixed type for interfaces defined in the base class for subclasses

I have a BaseList container that takes BaseItem‘s as items. I then derive a new list CustomList and I want it to hold CustomItem‘s.

How to I type methods in BaseList many methods to accept BaseItem. And also tell it to use CustomItem the derived list CustomList?

Here is the code I have so far:

from __future__ import annotations
from typing import List, Union, Iterator, overload


class BaseItem:
    pass


class BaseList:
    def __init__(self) -> None:
        self.items: List[BaseItem] = []

    def add_item(self, item: BaseItem) -> None:
        self.items.append(item)

    def __iter__(self) -> Iterator[BaseItem]:
        return iter(self.items)


class CustomItem(BaseItem):
    def __init__(self, value: int) -> None:
        self.value: int = value


class CustomList(BaseList):
    def sum(self) -> int:
        # mypy error: "BaseItem" has no attribute "value"
        return sum(item.value for item in self)
# no error, but it should say: incompatible type; expected "CustomItem".
CustomList().add_item(BaseItem())

container = CustomList()
container.add_item(CustomItem(10))
for item in container:
    # mypy error: `"BaseItem" has no attribute "value"`
    print(item.value)

Question

How can I define a type A and say in BaseList it should resolve to BaseItem and in CustomList it should resolve to CustomItem?

Tried so far

I tried using TypeVar with the bounds attribute, but I didn’t seem to get it work.

Constraints

I have more than just one subclass so I don’t want to re-implement all these methods with different types. Also there are many more of these methods with similar problems, like __contains__, __getitem__, class specific items, etc.

The type hints should be also resolved by PyCharm, as the goal is to get correct code-complete there.

Advertisement

Answer

Make BaseList class Generic:

class BaseItem:
    pass


T = TypeVar('T', bound=BaseItem)


class BaseList(Generic[T]):

    def __init__(self) -> None:
        self.items: List[T] = []

    def add_item(self, item: T) -> None:
        self.items.append(item)

    def __iter__(self) -> Iterator[T]:
        return iter(self.items)


class CustomItem(BaseItem):

    def __init__(self, value: int) -> None:
        self.value: int = value


class CustomList(BaseList[CustomItem]):

    def sum(self) -> int:
        reveal_type(self.items)  # Mypy: Revealed type is "builtins.list[CustomItem*]"
        return sum(item.value for item in self)
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement