I have this following code which is a simplified version of an entity component system in python:
from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar, overload
if TYPE_CHECKING:
from collections.abc import Generator
T = TypeVar("T")
T1 = TypeVar("T1")
T2 = TypeVar("T2")
class Registry:
def __init__(self) -> None:
self._next_game_object_id = 0
self._components: dict[type[T], set[int]] = {}
self._game_objects: dict[int, dict[type[T], T]] = {}
@overload
def get_components(
self,
component: type[T],
) -> Generator[tuple[int, T], None, None]:
...
@overload
def get_components(
self,
component: type[T],
component_two: type[T1],
) -> Generator[tuple[int, T, T1], None, None]:
...
@overload
def get_components(
self,
component: type[T],
component_two: type[T1],
component_three: type[T2],
) -> Generator[tuple[int, T, T1, T2], None, None]:
...
def get_components(self, *components: type[T]) -> Generator[tuple[int, tuple[T, ...]], None, None]:
game_object_ids = set.intersection(
*(self._components[component] for component in components),
)
for game_object_id in game_object_ids:
yield game_object_id, tuple(
self._game_objects[game_object_id][component]
for component in components
)
However, I'm getting some mypy errors which I cannot figure out. One of which says that the TypeVar T
is unbound and another says that get_components
does not accept all possible arguments of signature 1, 2, and 3. How can I fix these errors?
Possible solution:
from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar, overload, Generic
if TYPE_CHECKING:
from collections.abc import Generator
T = TypeVar("T")
T1 = TypeVar("T1")
T2 = TypeVar("T2")
class Registry(Generic[T, T1, T2]):
def __init__(self) -> None:
self._next_game_object_id = 0
self._components: dict[type[T | T1 | T2], set[int]] = {}
self._game_objects: dict[int, dict[type[T | T1 | T2], T | T1 | T2]] = {}
@overload
def get_components(
self,
__component: type[T],
) -> Generator[tuple[int, tuple[T]], None, None]:
...
@overload
def get_components(
self,
__component: type[T],
__component_two: type[T1],
) -> Generator[tuple[int, tuple[T, T1]], None, None]:
...
@overload
def get_components(
self,
__component: type[T],
__component_two: type[T1],
__component_three: type[T2],
) -> Generator[tuple[int, tuple[T, T1, T2]], None, None]:
...
def get_components(
self,
*components: type[T | T1 |T2]
) -> Generator[tuple[int, tuple[T | T1 | T2, ...]], None, None]:
game_object_ids = set.intersection(
*(self._components[component] for component in components),
)
for game_object_id in game_object_ids:
yield game_object_id, tuple(
self._game_objects[game_object_id][component]
for component in components
)
Explanations:
typing.Generic
binds the TypeVar
s to the class through making it a generic class which depends on those TypeVar
s (More on Generics)get_components
is crucial to accept all the different types that can occur through the overloadsT
, T, T1
and T, T1, T2
inside a tuple[...]
as this ensures consistency with the return annotation of the now generalized method definition@overload
ed methods positional only. This behaviour of *args
and @overload
seems not very well documented, only through a resolved issue on GitHub