That’s an interesting solution, I hadn’t considered overload for this use case. It wouldn’t cover all cases that a new Initialized
type would, but it would cover the biggest motivating factor.
Unfortunately I don’t think overload in its current form is a solution. Catenating your overload with the original example, mypy doesn’t stop the use of None
from typing import overload
@overload
def list_logger(level: str = "INFO", contents: None = None):
...
@overload
def list_logger(level: str = "INFO", contents: list[str] = None):
# had to give contents a default. strangely mypy didn't care it was again None here
...
def list_logger(level: str = "INFO", contents: list[str] | None = None):
if contents is None:
contents = []
print((level+":"), "\n".join(contents))
list_logger(contents=["just an idea"])
list_logger(contents=None)
list_logger(contents="ABC")
$ python -m mypy my.py
my.py:18: error: No overload variant of "list_logger" matches argument type "str"
my.py:18: note: Possible overload variants:
my.py:18: note: def list_logger(level: str = ..., contents: None = ...) -> Any
my.py:18: note: def list_logger(level: str = ..., contents: Optional[List[str]] = ...) -> Any
Playing with this idea further, I came up with this, which gave the desired type errors:
from typing import overload
@overload
def list_logger(level: str = "INFO", contents: list[str] = []):
...
@overload
def list_logger(level: str = "INFO", contents: tuple[str] = ("",)):
...
def list_logger(level = "INFO", contents = None):
if contents is None:
contents = []
print((level+":"), "\n".join(contents))
list_logger(contents=["just an idea"])
list_logger(contents=None)
list_logger(contents="ABC")
$ python -m mypy my.py
my.py:17: error: No overload variant of "list_logger" matches argument type "None"
my.py:17: note: Possible overload variants:
my.py:17: note: def list_logger(level: str = ..., contents: List[str] = ...) -> Any
my.py:17: note: def list_logger(level: str = ..., contents: Tuple[str] = ...) -> Any
my.py:18: error: No overload variant of "list_logger" matches argument type "str"
my.py:18: note: Possible overload variants:
my.py:18: note: def list_logger(level: str = ..., contents: List[str] = ...) -> Any
my.py:18: note: def list_logger(level: str = ..., contents: Tuple[str] = ...) -> Any
Found 2 errors in 1 file (checked 1 source file)
Yes, this is ugly: I couldn’t use None
as a default in the overloaded definitions because then mypy accepted it as a valid call type. I also couldn’t use just one overloaded definition because that raises a mypy error error: Single overload definition, multiple required
.
This would actually be a nice solution not involving any new python types, if type checkers allowed a single overload definition, and if they ignored the actual type assigned to kwargs in overloads (it currently seems to not care about the actual type in the final function definition, neither adding it to the valid types, nor raising an error about it not matching annotated definitions).