2
class B:
    pass


class InheritsB1(B):
    pass


class InheritsB2(B):
    pass


class A:
    prop: list[B]


class InheritsA1(A):
    prop: list[InheritsB1]


class InheritsA2(A):
    prop: list[InheritsB2]

With this code mypy raises Incompatible types in assignment (expression has type "List[InheritsB2]", base class "A" defined the type as "List[B]").

How can I make this work?

InheritsB1 is a subclass of B, so list[InheritsB1] is always a list of B. How can I tell mypy that it's not incompatible? Or, how can I tell mypy that the prop in A is "list of B or any specific subclass of B"?

I understand the issue here: mypy trouble with inheritance of objects in lists. But in this case I want the prop object to be a list of a specific instances (B or any subclass of B). I know it will never be mixed, as in it will always be list[B] or list[SubclassOfB1] or list[SubclassOfB2], never list[SubclassOfB1 | SubclassOfB2]. How can I do this?

Daniil Fajnberg
  • 12,753
  • 2
  • 10
  • 41
Some Guy
  • 576
  • 1
  • 4
  • 17
  • Does this answer your question? [Why does mypy not accept a list\[str\] as a list\[Optional\[str\]\]?](https://stackoverflow.com/questions/69479559/why-does-mypy-not-accept-a-liststr-as-a-listoptionalstr) – STerliakov Feb 01 '23 at 17:18
  • Also this: https://stackoverflow.com/questions/53275080/mypy-creating-a-type-that-accepts-list-of-instances-of-subclasses – STerliakov Feb 01 '23 at 17:20
  • No, those questions don't really explain how to fix the issue of List not being valid, which the answer to this question does. The solution was to create classes with a generic type. – Some Guy Feb 02 '23 at 08:38

1 Answers1

1

You linked a post that essentially addresses the issue, but you seem to still be confused:

InheritsB1 is a subclass of B, so list[InheritsB1] is always a list of B.

This is not true. To quote from this section of PEP 484:

By default generic types are considered invariant in all type variables

The theory section on variance in PEP 483 attempts to explain the concepts in much greater detail. I suggest you read through PEP 483 as well as PEP 484, if you want to get more serious about type safety.

The mypy complaint is justified and unless you want to follow the tip provided by mypy and change the type to something covariant like Sequence instead, you will have to work a bit more with generics yourself.


How best to solve your particular conundrum depends on other factors around your classes that are still ambiguous in your original post.

But one option might be to make A generic in terms of a type variable that has an upper bound of B. Then, if you want to define a subclass of A to hold elements of a specific subtype of B, there would be no problem:

from typing import Generic, TypeVar


_BType = TypeVar("_BType", bound="B")


class B:
    pass


class SubB(B):
    pass


class A(Generic[_BType]):
    attribute: list[_BType]


class SubA(A[SubB]):
    # attribute: list[SubB]
    ...


reveal_type(SubA().attribute)  # Revealed type is "builtins.list[SubB]"

Note that in this setup, you don't even have to override/annotate SubA.attribute (which is why I commented it out) because you specify the type argument for A to be SubB during inheritance, which means that SubA is no longer generic and attribute will always be inferred as list[SubB].

The code is type safe (passes mypy --strict), but it still may or may not be practical for you, depending on what other requirements you have for the classes involved. If you provide more details, I can try to amend my answer to better suit your setup.

Daniil Fajnberg
  • 12,753
  • 2
  • 10
  • 41
  • This kind of TypeVar declaration was exactly what I was looking for, thank you. Declaring the original attribute as Sequence[B] instead of List[B] also seems to work, but I prefer your solution because it allows me to then call subclasses with the right property subclass at definition time rather than adding hints to every class. – Some Guy Feb 01 '23 at 13:22