Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List[subclass] is incompatible with List[superclass] #2984

Closed
Stiivi opened this issue Mar 10, 2017 · 5 comments
Closed

List[subclass] is incompatible with List[superclass] #2984

Stiivi opened this issue Mar 10, 2017 · 5 comments

Comments

@Stiivi
Copy link

Stiivi commented Mar 10, 2017

When a function that is accepting a list of a base class (for example class Base) is passed and argument which is a list of subclass instances of the base class (for example Thing(Base)) mypy complains about incompatible type: Error: Argument 1 to "function" has incompatible type List[Thing]; expected List[Base].

Wrapping the argument with a cast to a list of the base class passes.

Code example:

from typing import List, cast

class Base:
    pass

class Thing(Base):
    pass

def function(arg: List[Base]) -> None:
    pass

things:List[Thing] = [Thing()]

# Error: Argument 1 to "function" has incompatible type List[Thing]; expected List[Base]
function(things)

# Passes
function(cast(List[Base], things))
@gvanrossum
Copy link
Member

This is as designed, please read up on Liskov.

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 10, 2017

You can often use Sequence[x] instead of List[x] to get code like your example working. This works because Sequence is covariant and doesn't let you set items in the list, unlike List[x] which is invariant and allows the mutation of the list.

@JelleZijlstra
Copy link
Member

This is expected behavior and documented at http://mypy.readthedocs.io/en/latest/generics.html#variance-of-generic-types. Your example would fail with code like this:

things: List[Thing] = []

def function(arg: List[Base]) -> None:
    arg.append(Base())

function(things)

for thing in things:
    assert isinstance(thing, Thing)  # fails at runtime, but type checker detects no errors

If your function doesn't actually modify the list, you can get around this error by typing the argument as Sequence[Base] or even Iterable[Base] instead.

Dietr1ch added a commit to Dietr1ch/Search-py that referenced this issue Sep 7, 2021
We need to ignore numpy and to heavily use assertions/exceptions to down-scope
the types we actually use without violating the Liskov principle.
This wouldn't be necessary if we had proper metaclasses :/ maybe we should look
into ABC.

The List[B] -> Sequence[B] changes are because Sequences, unlike Lists are
covariant and allow specializing the type of their elements.
This is WAI python/mypy#2984
@randolf-scholz
Copy link
Contributor

@JelleZijlstra So how should one type hint a list of subclasses of a base class, that is created using append? This seems like a very common case.

@eykamp
Copy link

eykamp commented Feb 17, 2022

Old issue, but here is a solution that works:

from typing import List, TypeVar

class Base:
    pass

class Thing(Base):
    pass


T = TypeVar("T", bound=Base)


def function(arg: List[T]) -> None:
    pass


things: List[Thing] = [Thing()]

function(things)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants