I have a Python function with an input parameter, the value of which controls the type of the return value. The parameter may be omitted (default value None
), in which case a module level variable is used. This way, the default behavior can be changed by changing the module level variable.
A minimal example:
from typing import overload, Union, Literal, Optional
option_default: Literal["str", "int"] = "int"
@overload
def test(option: Literal["str"]) -> str:
...
@overload
def test(option: Literal["int"]) -> int:
...
@overload
def test(option: Literal[None] = None) -> Union[str, int]:
...
def test(option: Optional[Literal["str", "int"]] = None) -> Union[str, int]:
if option is None:
option = option_default
if option == "str":
return "foo"
else:
return 1
foo: int = test() # Incompatible types in assignment (expression has type "Union[str, int]", variable has type "int")
option_default = "str"
baz: str = test() # Incompatible types in assignment (expression has type "Union[str, int]", variable has type "str")
Normally one would use typing.overload
to deal with this sort of situation. However, for test()
without arguments that doesn't work--hence the mypy errors. Is there a better way to provide type info here?
That's not possible. There is a proposal on GitHub to add typeof
from TypeScript, but that's a different thing - it doesn't track mutable variables like you've shown.
I don't think such a feature can be implemented, even in theory. It brings a lot of new complexity into type checking. I can think of some problematic scenarios:
def foo(callback: Callable[[], int]):
some_module.definitely_returns_an_int = callback
option_default = "int"
foo(test) # ok?
option_default = "str"
Now foo
has saved a function that returns a string.
def ints_are_the_bomb():
global option_default
option_default = "int"
option_default = "str"
some_other_module.baz()
value = test()
Can you be sure about the type of value
? There's no guarantee that some_other_module.baz()
didn't call your_module.ints_are_the_bomb()
. So now you need to track all changes that can ever happen to your_module.option_default
, potentially across modules. And if client code (if this is a library) can change the flag, then it's just impossible.
To generalize, the type of a value (including functions) can't change when you mutate something. That could break distant code that also happens to have a reference to this object.