I'm trying to understand typing.overload
and have applied it in a simple case where I want a function that takes input x: Literal["foo", "bar"]
and returns the list [x]
.
I'd like mypy to type the resulting list as list[Literal["foo"]]
or list[Literal["bar"]]
depending on the value of x
.
I know I could achieve this with a TypeVar
, but I'd still like to understand why the code below fails with the following error:
test.py:14: error: Overloaded function implementation cannot produce return type of signature 1
test.py:14: error: Overloaded function implementation cannot produce return type of signature 2
from typing import Literal, overload
@overload
def f(x: Literal["foo"]) -> list[Literal["foo"]]:
...
@overload
def f(x: Literal["bar"]) -> list[Literal["bar"]]:
...
def f(x: Literal["foo", "bar"]) -> list[Literal["foo", "bar"]]:
return [x]
CodePudding user response:
Lists in Python are invariant. That means that, even if B
is a subtype of A
, there is no relation between the types list[A]
and list[B]
.
If list[B]
were allowed to be a subtype of list[A]
, then someone could come along and do this.
my_b_list: list[B] = []
my_a_list: list[A] = my_b_list
my_a_list.append(A())
print(my_b_list) # Oh no, a list[B] contains an A value!
If you plan to modify the returned list, then what you're doing isn't safe. End of story. If you plan to treat the list as immutable, then consider what operations you actually need, and you may be able to find a covariant supertype of list
in typing
.
For example, Sequence
is a popular choice. It supports iteration, random access, and length access, while explicitly not allowing mutation.
from typing import Literal, overload, Sequence
@overload
def f(x: Literal["foo"]) -> Sequence[Literal["foo"]]:
...
@overload
def f(x: Literal["bar"]) -> Sequence[Literal["bar"]]:
...
def f(x: Literal["foo", "bar"]) -> Sequence[Literal["foo", "bar"]]:
return [x]
(Note: typing.Sequence
is deprecated in Python 3.9; if you only plan to support 3.9 , you might use collections.abc.Sequence
instead)
CodePudding user response:
AFAIU your question, your actual implementation needs to provide a single type (str
), not multiple Literal
.
The following works correctly according to pyright
, and seems to provide the feature you are looking for (only allowing lists of either "foo"
or "bar"
, rejecting everything else).
from typing import Literal, overload
@overload
def f(x: Literal["foo"]) -> list[Literal["foo"]]:
...
@overload
def f(x: Literal["bar"]) -> list[Literal["bar"]]:
...
def f(x: str) -> list[str]:
return [x]
f("foo") # valid
f("bar") # valid
f("baz") # error
which cause the following error:
a.py:20:3 - error: Argument of type "Literal['baz']" cannot be assigned to parameter "x" of type "Literal['bar']" in function "f"
"Literal['baz']" cannot be assigned to type "Literal['bar']"