Why does Mypy report a subset of JSON as invalid?
Question:
I have a base class that returns a JSON type. I then have subclasses that return more specific types that should I think be valid JSON types, but Mypy reports an error:
error: Return type "List[Dict[str, JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner" [override]
Have I misunderstood something or am I exploring the limits of the type-checking implementation?
Full example:
from typing import TypeAlias
JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
class JsonReturner:
def return_json(self) -> JSON:
raise NotImplementedError("abstract base class")
class ListJsonReturner(JsonReturner):
def return_json(self) -> list[JSON]:
return []
class DictJsonReturner(JsonReturner):
def return_json(self) -> dict[str, JSON]:
return {}
class ListDictJsonReturner(JsonReturner):
def return_json(self) -> list[dict[str, JSON]]:
return []
class DictListJsonReturner(JsonReturner):
def return_json(self) -> dict[str, list[JSON]]:
return {}
$ mypy jsontypes.py
jsontypes.py:27: error: Return type "List[Dict[str, JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner" [override]
jsontypes.py:33: error: Return type "Dict[str, List[JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner" [override]
Answers:
This all has to do with variance. Read PEP-483 for more information.
Generic types like list
are invariant: a type list[T]
is not a subtype nor a supertype of list[U]
unless T
and U
are the same type.
Similarly, dict
is invariant because a two concrete dict types are the same only if their key and value types are the same.
Function types are covariant in their return type. For a fixed argument type, Callable[..., R1]
is a subtype of Callable[…, R2]when
R1is a subtype of
R2`.
Each component of a union type is a subtype of the union.
When you override a function, the type of the override must be a subtype of the parent function. As long as you don’t change the parameter types, this means the new return type must be a subtype of the original’s return type.
In your first two overrides, the return types are subtypes of JSON.
In the last two, they are not. list[dict[str, JSON]]
is not a subtype of JSON
because lists are invariant. dict[str, list[JSON]]
is not a subtype of JSON
because dicts are invariant.
I have a base class that returns a JSON type. I then have subclasses that return more specific types that should I think be valid JSON types, but Mypy reports an error:
error: Return type "List[Dict[str, JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner" [override]
Have I misunderstood something or am I exploring the limits of the type-checking implementation?
Full example:
from typing import TypeAlias
JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
class JsonReturner:
def return_json(self) -> JSON:
raise NotImplementedError("abstract base class")
class ListJsonReturner(JsonReturner):
def return_json(self) -> list[JSON]:
return []
class DictJsonReturner(JsonReturner):
def return_json(self) -> dict[str, JSON]:
return {}
class ListDictJsonReturner(JsonReturner):
def return_json(self) -> list[dict[str, JSON]]:
return []
class DictListJsonReturner(JsonReturner):
def return_json(self) -> dict[str, list[JSON]]:
return {}
$ mypy jsontypes.py
jsontypes.py:27: error: Return type "List[Dict[str, JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner" [override]
jsontypes.py:33: error: Return type "Dict[str, List[JSON]]" of "return_json" incompatible with return type "JSON" in supertype "JsonReturner" [override]
This all has to do with variance. Read PEP-483 for more information.
Generic types like list
are invariant: a type list[T]
is not a subtype nor a supertype of list[U]
unless T
and U
are the same type.
Similarly, dict
is invariant because a two concrete dict types are the same only if their key and value types are the same.
Function types are covariant in their return type. For a fixed argument type, Callable[..., R1]
is a subtype of Callable[…, R2]when
R1is a subtype of
R2`.
Each component of a union type is a subtype of the union.
When you override a function, the type of the override must be a subtype of the parent function. As long as you don’t change the parameter types, this means the new return type must be a subtype of the original’s return type.
In your first two overrides, the return types are subtypes of JSON.
In the last two, they are not. list[dict[str, JSON]]
is not a subtype of JSON
because lists are invariant. dict[str, list[JSON]]
is not a subtype of JSON
because dicts are invariant.