Overload based on optional argument #1326
-
I'm trying to add type hints to a function that is already part of a public API. It is structurally equivalent to: def thing(value_1, value_2 = None, include_extra=False):
if value_2 is None:
value_2 = "empty"
if include_extra:
return value_1, value_2, "extra"
return value_1, value_2 I would like to be able to indicate that the length of returned tuple depends on the value for
At first I tried the following @overload
def thing(
value_1: str, value_2: Optional[str] = None, include_extra: Literal[False] = False
) -> Tuple[str, str]:
...
@overload
def thing(
value_1: str, value_2: Optional[str] = None, include_extra: Literal[True] = True
) -> Tuple[str, str, str]:
... but
Also, when I tested it, the type of the return value when Is there a "correct" way to do this? Or is relying on the overload order and silencing the error the best I can do? |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
I think what you're looking for is this: from typing import Literal, overload
@overload
def thing(value_1: str, value_2: str | None = None) -> tuple[str, str]:
...
@overload
def thing(
value_1: str, value_2: str | None, include_extra: Literal[False]
) -> tuple[str, str]:
...
@overload
def thing(
value_1: str, value_2: str | None, include_extra: Literal[True]
) -> tuple[str, str, str]:
...
@overload
def thing(
value_1: str, value_2: str | None, include_extra: bool
) -> tuple[str, str] | tuple[str, str, str]:
...
def thing(
value_1: str, value_2: str | None = None, include_extra: bool = False
) -> tuple[str, str] | tuple[str, str, str]:
if value_2 is None:
value_2 = "empty"
if include_extra:
return value_1, value_2, "extra"
return value_1, value_2 |
Beta Was this translation helpful? Give feedback.
-
Eric's answer isn't fully correct, since it doesn't permit Here is something that should be exhaustively correct. It's a little bit annoying, but if you can change your API a little bit you have some opportunities to simplify.
But yes, definitely annoying. This was my suggestion for a way to fix that wouldn't involve changes to how Python parses code: https://mail.python.org/archives/list/[email protected]/message/3YKFEFTL5GJIEBQLQNIHVEVFWF2FIYNW/ |
Beta Was this translation helpful? Give feedback.
-
I know generalities like this aren't particularly useful to anyone, but this proliferation of overloads to exhaustively annotate a function, especially one with default arguments, is an extremely cumbersome requirement. In any non-trivial codebase you will have interfaces like this. One public one where this pattern is common is I keep on going back to this issue as an example: python/mypy#5486 In this discussion's example, I'm trying to understand what exactly is ambiguous if you were to permit default arguments across all overloads. Is it only which overload to use if all default arguments are omitted? If so, could we hypothetically could we assume it's the first overload? Also, could the bool overloaded be made unnecessary in some hypothetical change that makes the implementation interface (if annotated) a viable option? Ultimately, my follow-up question is: is there any context in which we could support only needing two overloads and type checkers do the rest? From a user's perspective the two overloads are what is intuitive and natural, I would argue. |
Beta Was this translation helpful? Give feedback.
Eric's answer isn't fully correct, since it doesn't permit
thing("value", include_extra=True)
Here is something that should be exhaustively correct. It's a little bit annoying, but if you can change your API a little bit you have some opportunities to simplify.