Given a variable with the type hint list[ParentItem]
, how can one assign another list to it with the type hint list[ChildItem]
, where ChildItem
is derived from ParentItem
, without triggering linter type checking errors?
Consider the following contrived, minimal example in which the pyright linter throws the following argument type checking error:
Argument of type
list[ChildItem]
cannot be assigned to parameteritems
of typelist[ParentItem]
in function__init__
list[ChildItem]
is incompatible withlist[ParentItem]
Type parameter_T@list
is invariant, butChildItem
is not the same asParentItem
Consider switching fromlist
toSequence
which is covariant
class ParentItem:
def __init__(self) -> None:
pass
class ChildItem(ParentItem):
def __init__(self) -> None:
super().__init__()
class ParentGroup:
def __init__(self, items: list[ParentItem]) -> None:
self._items = items
def add(self, item: ParentItem) -> None:
self._items.append(item)
class ChildGroup(ParentGroup):
def __init__(self, child_items: list[ChildItem]) -> None:
super().__init__(child_items) # pyright argument type checking error here
Changing the line:
def __init__(self, child_items: list[ChildItem]) -> None:
to
def __init__(self, child_items: list[ParentItem]) -> None:
superficially resolves the error, but this doesn't provide the desired type hints when the ChildGroup
class is used. The error reappears later anyway when one attempts to create a ChildGroup
instance using a list of ChildItem
instances, e.g.:
i1 = ChildItem()
i2 = ChildItem()
ilist = [i1, i2]
g_with_child_items = ChildGroup(ilist) # same error as before, but regarding assignment to "child_items" parameter instead of "items" parameter
How can the child_items
variable of the ChildGroup
class be correctly type annotated as list[ChildItem]
?
The solution is to create a user-defined generic type variable (TypeVar
) that is bound to the ParentItem
class, and then to use this type in the signatures of the ParentGroup
class' methods where applicable. This will allow ParentItem
and its derived classes to be used without type hinting errors in ParentGroup
and its derivatives. Note that ParentGroup
has to derive from the typing.Generic
base class, and then any generic types that are to be used within this class have to be specified (i.e. ParentItemType
as shown below).
from typing import Generic, TypeVar
ParentItemType = TypeVar("ParentItemType", bound="ParentItem")
class ParentItem:
def __init__(self) -> None:
pass
class ChildItem(ParentItem):
def __init__(self) -> None:
super().__init__()
class ParentGroup(Generic[ParentItemType]):
def __init__(self, items: list[ParentItemType]) -> None:
self._items = items
def add(self, item: ParentItemType) -> None:
self._items.append(item)
class ChildGroup(ParentGroup[ChildItem]):
def __init__(self, child_items: list[ChildItem]) -> None:
super().__init__(child_items)
The following now works as desired without linter errors:
i1 = ChildItem()
i2 = ChildItem()
ilist = [i1, i2]
g_with_child_items = ChildGroup(ilist)