Use list of derived class as list of base class in Python
Question:
I have a function which takes a list of a base class as argument, and I have a variable which is a list of a derived class. Using this variable as the argument gives mypy error: Argument 1 to "do_stuff" has incompatible type "List[DerivedClass]"; expected "List[BaseClass]".
class BaseClass(TypedDict):
base_field: str
class DerivedClass(BaseClass):
derived_field: str
def do_stuff(data: List[BaseClass]) -> None:
pass
foo: List[DerivedClass] = [{'base_field': 'foo', 'derived_field': 'bar'}]
do_stuff(foo)
If the argument and variable are instead BaseClass and DerivedClass respectively, i.e. not lists, it understands that the variable can be casted implicitly to the base class. But for lists it doesn’t work. How can I solve this, preferably other than #type: ignore.
Answers:
It depends on what exactly do_stuff
is doing, but nine times out of ten the best solution is to use Sequence
instead of List
:
from typing import Sequence
def do_stuff(data: Sequence[BaseClass]) -> None:
pass
The reason you can’t use List[BaseClass]
here is that do_stuff
would be allowed to add BaseClass
instances to data
, which would in turn break foo
in the caller. Sequence
doesn’t imply mutability, so do_stuff
is not allowed (static-typing-wise) to modify a Sequence
parameter, which prevents that issue. (Put differently, Sequence
is covariant and List
is invariant. Most mutable generics are invariant because of exactly this issue.)
If do_stuff
does need to mutate data
, you’ll need to rethink the typing — should it be allowed to add a BaseClass
to it? If not, maybe do_stuff
should take a List[DerivedClass]
. If so, you need to declare foo
as a List[BaseClass]
to account for that possibility.
You can try using a TypeVar and bound it to BaseClass. In code, that looks like
from typing import TypeVar
T = TypeVar('T', bound=BaseClass)
def do_stuff(data: list[T]) -> None:
pass
this also makes mypy happy
I have a function which takes a list of a base class as argument, and I have a variable which is a list of a derived class. Using this variable as the argument gives mypy error: Argument 1 to "do_stuff" has incompatible type "List[DerivedClass]"; expected "List[BaseClass]".
class BaseClass(TypedDict):
base_field: str
class DerivedClass(BaseClass):
derived_field: str
def do_stuff(data: List[BaseClass]) -> None:
pass
foo: List[DerivedClass] = [{'base_field': 'foo', 'derived_field': 'bar'}]
do_stuff(foo)
If the argument and variable are instead BaseClass and DerivedClass respectively, i.e. not lists, it understands that the variable can be casted implicitly to the base class. But for lists it doesn’t work. How can I solve this, preferably other than #type: ignore.
It depends on what exactly do_stuff
is doing, but nine times out of ten the best solution is to use Sequence
instead of List
:
from typing import Sequence
def do_stuff(data: Sequence[BaseClass]) -> None:
pass
The reason you can’t use List[BaseClass]
here is that do_stuff
would be allowed to add BaseClass
instances to data
, which would in turn break foo
in the caller. Sequence
doesn’t imply mutability, so do_stuff
is not allowed (static-typing-wise) to modify a Sequence
parameter, which prevents that issue. (Put differently, Sequence
is covariant and List
is invariant. Most mutable generics are invariant because of exactly this issue.)
If do_stuff
does need to mutate data
, you’ll need to rethink the typing — should it be allowed to add a BaseClass
to it? If not, maybe do_stuff
should take a List[DerivedClass]
. If so, you need to declare foo
as a List[BaseClass]
to account for that possibility.
You can try using a TypeVar and bound it to BaseClass. In code, that looks like
from typing import TypeVar
T = TypeVar('T', bound=BaseClass)
def do_stuff(data: list[T]) -> None:
pass
this also makes mypy happy