mypy 不理解为 None 值引发异常

mypy doesn't undestand raise Exception for None value

我在另一个函数中创建一个函数,如果其中一个变量是 None 以引发 ValueError。但是mypy看不懂,我的代码是:

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 不理解 make_setting_of_policy 的输入不是 None,并显示以下错误:

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"

mypy doesn't understand the input of make_setting_of_policy is not None

是的,问题是关于 type narrowing Optional 参数以删除潜在的 None。 Mypy 不考虑引发 run-time 异常。最简单的解决方案是直接检查参数(这应该适用于所有支持 PEP 484 的 Python 3.5+ 版本。)

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}

mypy 结果:

Success: no issues found in 1 source file

如果使用 Python 3.10,您可以使用 TypeGuards,注意其特定规则。

PEP 647 - TypeGuard Type

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.

使用 TypeGuard 的解决方案(使用单个参数,因此我们不重复前面示例的缩小)如下所示:

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}

mypy 结果:

Success: no issues found in 1 source file

取消注释 reveal_type() 调用使 mypy 显示预期的类型缩小:

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]"


但是您的问题中有一个微妙的暗示,即在 def check(the_first_name,*arg) 函数中对 *args 进行类型缩小。使用 Python 3.11 可以使用 PEP 646 -- Variadic Generics, but since your Optionals 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.