Suppose I have a Child
class that is a subclass of Parent
class, and a function that accepts a list of instances of Parent
subclasses:
from typing import List
class Parent:
pass
class Child(Parent):
pass
def func(objects: List[Parent]) -> None:
print(objects)
children = [Child()]
func(children)
running mypy
on this produces an error:
error: Argument 1 to "func" has incompatible type "List[Child]"; expected "List[Parent]"
How do I create a type for this?
P.S. There's a way to fix this particular error with a Sequence
type:
def func(objects: Sequence[Parent]) -> None:
print(objects)
but this doesn't help in other similar cases. I need a List
, not a Sequence
.
Passing in a list here is fundamentally not type-safe. For example, what if you do this?
def func(objects: List[Parent]) -> None:
print(objects)
objects.append(Parent())
children: List[Child] = [Child(), Child(), Child()]
func(children)
# Uh-oh! 'children' contains a Parent()!
If this were permitted to type check, your code would end up containing a bug.
To use type-jargon, List
is intentionally designed to be an invariant type. That is, even though Child
is a subclass of Parent
, it is not the case that List[Child]
is a subtype of List[Parent]
, or vice-versa. You can find more info about invariance here and here.
The most common alternative is to use Sequence
instead, which is a read-only interface/protocol/whatever. And since Sequence is read-only, it's safe for it to be covariant: that is, Sequence[Child]
is considered to be a valid subtype of Sequence[Parent]
.
Depending on what exactly you're doing, you may be able to use type variables instead. E.g. instead of saying "this function takes in a list of Parent", you say "this function takes in a list of any class which is Parent, or a subclass of Parent":
TParent = TypeVar('TParent', bound=Parent)
def func(objects: List[TParent]) -> List[TParent]:
print(objects)
# Would not typecheck: we can't assume 'objects' will be a List[Parent]
objects.append(Parent())
return objects
Depending on what exactly you're doing, you could maaaaaaaaybe create a custom Protocol that defines a write-only list-like collection (or a custom data structure). And since your data structure would be write-only, you could make it contravariant -- that is, WriteOnlyThing[Parent]
would be a subtype of WriteOnlyThing[Child]
. You then make func
accept WriteOnlyThing[Child]
and could safely pass in instances of both WriteOnlyThing[Child]
and WriteOnlyThing[Parent]
.
If neither approach works in your case, your only recourse is to either use # type: ignore
to silence the error (not recommended), give up on type-checking the contents of the list and make the argument of type List[Any]
(also not recommended), or figure out how to restructure your code so it's type-safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With