You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
default value for overloaded optional parameters #7333
I'm trying to use Literal and @overloadto change the result type of a function depending on a boolean switch.
This issue is not specific to Literal, but it makes it easier to produce an easy to understand real-life use case.
Everything works great as long as the parameter is mandatory:
from typing import overload, Optional
from typing_extensions import Literal
@overload
def plusone(x: Optional[int], none_to_zero: Literal[True]) -> int:
...
@overload
def plusone(x: Optional[int], none_to_zero: Literal[False]) -> Optional[int]:
...
def plusone(x, none_to_zero):
if x is None:
if none_to_zero:
x = 0
else:
return None
return x + 1
plusone(None, True).to_bytes(4, "little") # ok
plusone(None, False).to_bytes(4, "little") # not ok
mypy output: 25: error: Item "None" of "Optional[int]" has no attribute "to_bytes"
However, if I set a default value:
from typing import overload, Optional
from typing_extensions import Literal
@overload
def plusone(x: Optional[int], none_to_zero: Literal[True] = True) -> int:
...
@overload
def plusone(x: Optional[int], none_to_zero: Literal[False] = True) -> Optional[int]:
...
def plusone(x, none_to_zero=True):
if x is None:
if none_to_zero:
x = 0
else:
return None
return x + 1
plusone(None, True).to_bytes(4, "little") # ok
plusone(None, False).to_bytes(4, "little") # not ok
plusone(None).to_bytes(4, "little") # ok
mypy output:
11: error: Incompatible default for argument "none_to_zero" (default has type "Literal[True]", argument has type "Literal[False]")
25: error: Item "None" of "Optional[int]" has no attribute "to_bytes"
Workaround
Add # type: ignore on line 11. The error on line 25 is still reported.
Expected behaviour
In each of the signatures of an @overload'ed function, the allowed default values of an optional parameter should be the Union of type annotations from all the signatures.
You should only set the default on one of the overloads (the one that will get taken if the argument is not given). Otherwise mypy does not know which overload to use if the defaulted argument is not given.
Reacted by wouter bolsterlee, Daniel Malachov, Arno Gobbin, anderium and Antoine Chopin
You should only set the default on one of the overloads (the one that will get taken if the argument is not given). Otherwise mypy does not know which overload to use if the defaulted argument is not given.
I just stumbled upon this as well. It seems to me that you can solve this by adding a * before the parameter that is actually optional in one of the overloads.
You will need to alternately re-order the arguments for that to work, since the required literal must be the only thing after * for each overload. Hope this helps!
Reacted by Maxim Stykow, Hanusz Leszek, Errol Lloyd, Felix Fontein, Siddhartha Gandhi, haruna, Jan Havlín, justinearl and Ben Saufley
@JelleZijlstra can this issue be re-opened? Clearly the solution in #7333 (comment) doesn't work according to #7333 (comment), and #7333 (comment) seems like a work-around which probably doesn't work properly with non-keyword arguments in function calls given the *?
Hmm... can you give a working example? Testing what is described in #7333 (comment) results in:
error: Overloaded function signature 3 will never be matched: signature 2's parameter type(s) are the same or broader [misc]
error: Overloaded function implementation does not accept all possible arguments of signature 1 [misc]
error: Overloaded function implementation does not accept all possible arguments of signature 2 [misc]
Well @rggjan, my take away from @stianjenson (which I don't think I ever would have though of on my own ... thanks!) is that keyword only arguments is simply the cost of the ticket out of strife with overloads and default values. Personally, I'm happy to make all arguments involved in the overload variations keyword-only, as default-values no longer have any ordering requirement.
And yes, function calls will need argument keywords ... but I think avoiding that is the kind of developer laziness/easiness that really doesn't have much or any value given the cost involved.
Activity
JelleZijlstra commentedon Aug 13, 2019
You should only set the default on one of the overloads (the one that will get taken if the argument is not given). Otherwise mypy does not know which overload to use if the defaulted argument is not given.
JohnVillalovos commentedon Feb 27, 2021
But then you can get an error like:
Code I'm trying it on:
stianjensen commentedon Mar 1, 2021
I just stumbled upon this as well. It seems to me that you can solve this by adding a * before the parameter that is actually optional in one of the overloads.
So, something like:
You will need to alternately re-order the arguments for that to work, since the required literal must be the only thing after * for each overload. Hope this helps!
rggjan commentedon May 10, 2022
@JelleZijlstra can this issue be re-opened? Clearly the solution in #7333 (comment) doesn't work according to #7333 (comment), and #7333 (comment) seems like a work-around which probably doesn't work properly with non-keyword arguments in function calls given the
*?JelleZijlstra commentedon May 10, 2022
@stianjensen's workaround should work in all cases, although it may need a lot of tricky overloads.
rggjan commentedon May 11, 2022
Hmm... can you give a working example? Testing what is described in #7333 (comment) results in:
for me...
maegul commentedon Jul 14, 2022
Well @rggjan, my take away from @stianjenson (which I don't think I ever would have though of on my own ... thanks!) is that keyword only arguments is simply the cost of the ticket out of strife with overloads and default values. Personally, I'm happy to make all arguments involved in the overload variations keyword-only, as default-values no longer have any ordering requirement.
And yes, function calls will need argument keywords ... but I think avoiding that is the kind of developer laziness/easiness that really doesn't have much or any value given the cost involved.