I'm having problems with generics and type narrowing in python. What I want is a generic list type and a filter function that selects elements by type. I have two goals:
- The type argument of the filter function should be restricted to the possible types in the generic list.
- The return type of the filter function should be as narrow as possible.
So far, I can only come up with solutions that achieve one of the goals but never both at once. Here is a code example:
from typing import Generic, TypeVar, Type
T = TypeVar('T')
V = TypeVar('V')
class FooList(Generic[T]):
def __init__(self) -> None:
self.data: list[T] = []
def filter(self, type__: Type[T]):
return [d for d in self.data if isinstance(d, type__)]
class BarList(Generic[T]):
def __init__(self) -> None:
self.data: list[T] = []
def filter(self, type__: Type[V]) -> list[V]:
return [d for d in self.data if isinstance(d, type__)]
lst = FooList[int | str]()
lst.filter(float) # good: does not typecheck
filtered = lst.filter(int) # bad: filtered: list[int | str]
lst = BarList[int | str]()
lst.filter(float) # bad: does typecheck
filtered = lst.filter(int) # good: filtered: list[int]
# goal:
lst = BazList[int | str]()
lst.filter(float) # good: does not typecheck
filtered = lst.filter(int) # good: filtered: list[int]
I would expect a possible solution to define the function with T as argument and a narrower V as return type, but I don't know how to express this. Binding V to T (i.e. V = TypeVar('V', bound=T)) does not seem to work, as TypeVar bound type cannot be generic.