Reputation: 317
I create a function in another function if one of the variables is None
to raise ValueError
. But mypy doesn't understand it, my code is:
from typing import Mapping, Optional, Union
def make_setting(name: str, age: Optional[int], score: Optional[int]) -> Mapping[str, Union[str, int]]:
def check(the_first_name,*arg):
if None in arg:
raise ValueError('The args of "{}" should not be None.'.format(the_first_name))
check(name, age, score)
return make_setting_of_policy(name, age, score)
def make_setting_of_policy(name: str, age:int, score: int) -> Mapping[str, Union[str, int]]:
return {'name':name, 'age':age, 'score':score}
mypy doesn't understand the input of make_setting_of_policy
is not None
, and shows the below error:
Argument 2 to "make_setting_of_policy" has incompatible type "Optional[int]"; expected "int"
Argument 3 to "make_setting_of_policy" has incompatible type "Optional[int]"; expected "int"
Upvotes: 3
Views: 2396
Reputation: 12930
mypy doesn't understand the input of
make_setting_of_policy
is notNone
True, the question is about type narrowing the Optional
arguments to remove the potential None
. Mypy doesn't take the raising of a run-time exception into account. The simplest solution is to check the arguments directly (this should work for all Python 3.5+ versions supporting PEP 484.)
from typing import Mapping, Optional, Union
def make_setting(name: str, age: Optional[int], score: Optional[int]) -> Mapping[str, Union[str, int]]:
if age is not None and score is not None:
return make_setting_of_policy(name, age, score) # runtime type check
else:
raise ValueError('The args of "{}" should not be None.'.format(name)) # runtime exception
def make_setting_of_policy(name: str, age: int, score: int) -> Mapping[str, Union[str, int]]:
return {'name': name, 'age': age, 'score': score}
The mypy result:
Success: no issues found in 1 source file
If using Python 3.10 you could use TypeGuards minding its specific rules.
When a conditional statement includes a call to a user-defined type guard function, and that function returns true, the expression passed as the first positional argument to the type guard function should be assumed by a static type checker to take on the type specified in the TypeGuard return type, unless and until it is further narrowed within the conditional code block.
A solution using TypeGuard
(using a single argument so we don't repeat the narrowing of the previous example) look like this:
from typing import Mapping, Optional, Union, TypeGuard
def check(args: tuple[str, Optional[int], Optional[int]]) -> TypeGuard[tuple[str, int, int]]:
if None in args:
raise ValueError('The args of "{}" should not be None.'.format(args[0]))
return all(x is not None for x in args)
def make_setting(name: str, age: Optional[int], score: Optional[int]) -> Mapping[str, Union[str, int]]:
the_args = (name, age, score)
# reveal_type(the_args)
if check(the_args):
# reveal_type(the_args)
return make_setting_of_policy(*the_args) # runtime type check
else:
raise ValueError('The args of "{}" should not be None.'.format(name)) # runtime exception
def make_setting_of_policy(name: str, age: int, score: int) -> Mapping[str, Union[str, int]]:
return {'name': name, 'age': age, 'score': score}
The mypy result:
Success: no issues found in 1 source file
Uncommenting the reveal_type()
calls has mypy reveal the intended type narrowing:
Q71223933.py:13: note: Revealed type is "Tuple[builtins.str, Union[builtins.int, None], Union[builtins.int, None]]"
Q71223933.py:15: note: Revealed type is "Tuple[builtins.str, builtins.int, builtins.int]"
But there's a subtle hint in your question to having type narrowing done on *args
in the def check(the_first_name,*arg)
function. With Python 3.11 it can be done using PEP 646 -- Variadic Generics, but since your Optional
s where written with Python 3.9 syntax I'm assuming that's beyond the scope of this question, see PEP 604 -- Allow writing union types as X | Y.
Upvotes: 3