如何告诉 python 类型检查器一个可选的肯定存在?

how to tell a python type checker that an optional definitely exists?

我习惯打字稿,其中可以使用 ! 告诉类型检查器假定值不会为空。在 python 中使用类型注释时是否有类似的东西?

一个(人为的)例子:

在下面的代码中执行表达式 m.maybe_num + 3 时,封闭的 if 保证 maybe_num 不会是 None。但是类型检查器并不知道这一点,并且 returns 是一个错误。 (在 https://mypy-play.net/?mypy=latest&python=3.10. 中验证)我如何告诉类型检查器我知道的更多?

from typing import Optional

class MyClass:

    def __init__(self, maybe_num: Optional[int]):
        self.maybe_num = maybe_num
        
    def has_a_num(self) -> bool:
        return self.maybe_num is not None

    def three_more(self) -> Optional[int]:
        if self.has_a_num:
            # mypy error: Unsupported operand types for + ("None" and "int")
            return self.maybe_num + 3
        else:
            return None

遗憾的是,没有一种干净的方法可以从这样的函数调用中推断出某些东西的类型,但是您可以使用 has_a_num() 方法的 TypeGuard 注释来施展魔法,尽管这些方法的好处除非差异比单个 int 的类型大得多,否则不会真正感受到注释。如果它只是一个单一的值,你应该只使用一个标准而不是 None 检查。

if self.maybe_num is not None:
    ...

您可以定义主要子类的子类,其中明确重新声明其类型受影响的任何参数的类型。

class MyIntClass(MyClass):
    maybe_num: int

从那里开始,您的检查器函数应该仍然是 return 布尔值,但是带注释的 return 类型告诉 MyPy 它应该使用它来将类型缩小到列出的类型。

遗憾的是,它只会对适当的函数参数执行此操作,而不是隐式的 self 参数,但这可以通过显式提供 self 来轻松解决,如下所示:

if MyClass.has_a_num(self):
    ...

该语法很糟糕,但它适用于 MyPy。

这使得完整的解决方案如下

# Parse type annotations as strings to avoid 
# circular class references
from __future__ import annotations
from typing import Optional, TypeGuard

class MyClass:
    def __init__(self, maybe_num: Optional[int]):
        self.maybe_num = maybe_num

    def has_a_num(self) -> TypeGuard[_MyClass_Int]:
        # This annotation defines a type-narrowing operation,
        # such that if the return value is True, then self
        # is (from MyPy's perspective) _MyClass_Int, and 
        # otherwise it isn't
        return self.maybe_num is not None

    def three_more(self) -> Optional[int]:
        if MyClass.has_a_num(self):
            # No more mypy error
            return self.maybe_num + 3
        else:
            return None

class _MyClass_Int(MyClass):
    maybe_num: int

TypeGuard 是在 Python 3.10 中添加的,但可以使用来自 pip.

typing_extensions 模块在早期版本中使用