How do I annotate a function that takes AnyStr with a str default value?
Question:
I wish to type-annotate a function that takes an AnyStr
argument that defaults to a str
and also returns an AnyStr
of the same type. However, if I write this:
from typing import AnyStr
def func(s: AnyStr = ".") -> AnyStr:
return s
then mypy fails with "Incompatible default for argument "s" (default has type "str", argument has type "bytes")
".
I also tried splitting the code into a .py
and .pyi
file like so:
.py
file:
def func(s = "."):
return s
.pyi
file:
from typing import AnyStr
def func(s: AnyStr = ...) -> AnyStr:
...
… but I must be invoking mypy wrong, because it fails to type-check the invocations of func
; e.g., if I add func(42)
to the .py
file, mypy doesn’t complain.
What is the correct way to annotate my function and get the code to be completely type-checked?
Answers:
Use None
as the default value, then inside the function use two casts: one to replace None
with "." cast as an Optional[AnyStr]
, and one to cast the return value as an AnyStr
.
def func(s : AnyStr = None) -> AnyStr:
if s is None:
s = cast(Optional[AnyStr], ".")
return cast(AnyStr, s)
EDIT: My original answer fails, as the type check ignores the concrete implementation:
One possibility is to use overload
to enumerate the two cases covered by AnyStr
:
from typing import overload
@overload
def func(s: str = ".") -> str:
pass
@overload
def func(s: bytes) -> bytes:
pass
def func(s):
return s
This case was discussed here. Guido’s answer in particular:
I think the problem here is that in general, e.g. if there are other parameters also using _T
in their type, the default value won’t work. Since this is the only parameter, you could say that this is overly restrictive, and if there are no parameters, the return type should just be determined by that default value (in your simplified example, you’d want it to return int
). But I’m not so keen on doing that, since it breaks as soon as another generic parameter is used.
Based on that thread, you can use the following workaround:
from typing import AnyStr, overload
@overload
def func() -> str: ...
@overload
def func(s: AnyStr) -> AnyStr: ...
def func(s = "."): # unchecked implementation
return func_internal(s)
def func_internal(s: AnyStr) -> AnyStr:
# a checked complicated body moved here
return s
Perhaps this is what you want:
@overload
def func(s: str=...) -> str: ...
@overload
def func(s: bytes) -> bytes: ...
@overload
def func(s: None) -> str: ...
def func(s="."):
return s
r1 = func()
reveal_type(r1)
r2 = func("1")
reveal_type(r2)
r3 = func(b"1")
reveal_type(r3)
Mypy: The stdout of the command line is:
note: Revealed type is 'builtins.str'
note: Revealed type is 'builtins.str'
note: Revealed type is 'builtins.bytes'
I wish to type-annotate a function that takes an AnyStr
argument that defaults to a str
and also returns an AnyStr
of the same type. However, if I write this:
from typing import AnyStr
def func(s: AnyStr = ".") -> AnyStr:
return s
then mypy fails with "Incompatible default for argument "s" (default has type "str", argument has type "bytes")
".
I also tried splitting the code into a .py
and .pyi
file like so:
.py
file:
def func(s = "."):
return s
.pyi
file:
from typing import AnyStr
def func(s: AnyStr = ...) -> AnyStr:
...
… but I must be invoking mypy wrong, because it fails to type-check the invocations of func
; e.g., if I add func(42)
to the .py
file, mypy doesn’t complain.
What is the correct way to annotate my function and get the code to be completely type-checked?
Use None
as the default value, then inside the function use two casts: one to replace None
with "." cast as an Optional[AnyStr]
, and one to cast the return value as an AnyStr
.
def func(s : AnyStr = None) -> AnyStr:
if s is None:
s = cast(Optional[AnyStr], ".")
return cast(AnyStr, s)
EDIT: My original answer fails, as the type check ignores the concrete implementation:
One possibility is to use overload
to enumerate the two cases covered by AnyStr
:
from typing import overload
@overload
def func(s: str = ".") -> str:
pass
@overload
def func(s: bytes) -> bytes:
pass
def func(s):
return s
This case was discussed here. Guido’s answer in particular:
I think the problem here is that in general, e.g. if there are other parameters also using
_T
in their type, the default value won’t work. Since this is the only parameter, you could say that this is overly restrictive, and if there are no parameters, the return type should just be determined by that default value (in your simplified example, you’d want it to returnint
). But I’m not so keen on doing that, since it breaks as soon as another generic parameter is used.
Based on that thread, you can use the following workaround:
from typing import AnyStr, overload
@overload
def func() -> str: ...
@overload
def func(s: AnyStr) -> AnyStr: ...
def func(s = "."): # unchecked implementation
return func_internal(s)
def func_internal(s: AnyStr) -> AnyStr:
# a checked complicated body moved here
return s
Perhaps this is what you want:
@overload
def func(s: str=...) -> str: ...
@overload
def func(s: bytes) -> bytes: ...
@overload
def func(s: None) -> str: ...
def func(s="."):
return s
r1 = func()
reveal_type(r1)
r2 = func("1")
reveal_type(r2)
r3 = func(b"1")
reveal_type(r3)
Mypy: The stdout of the command line is:
note: Revealed type is 'builtins.str'
note: Revealed type is 'builtins.str'
note: Revealed type is 'builtins.bytes'