Python 断言前的可选类型

Python optional types before assertion

我正在使用 Python 类型提示和 mypy,代码如下:

from typing import Optional


def compute(
    use_wcog: bool = False,
    Xt: Optional[float] = None,
    Xd: Optional[float] = None,
    Xw: Optional[float] = None,
    Xs: Optional[float] = None,
):

    if use_wcog:
        reqs = (Xt, Xd, Xw)
        assert all([_ is not None for _ in reqs])
        res = ((Xt**2 + Xw**2) / Xd)**2
    else:
        reqs = (Xs, Xd)
        assert all([_ is not None for _ in reqs])
        res = (Xs**2 / Xd)**2

    return res

我收到以下错误: ** 不支持的操作数类型(浮点数和 None)(mypy 错误)

正确的处理方式是什么?

Mypy 不够聪明,无法根据 all 和列表理解来缩小类型。简单点:

    if use_wcog:
        assert Xt and Xd and Xw
        res = ((Xt**2 + Xw**2) / Xd)**2
    else:
        assert Xs and Xd
        res = (Xs**2 / Xd)**2

FWIW,将这些依赖项嵌入到函数的主体中使得输入在很大程度上无用 IMO - 很容易以在运行时出错的方式调用此函数,而静态类型的全部意义在于可静态检测到的使用错误。鉴于该函数的两个实现在预期输入和返回结果方面具有完全不同的契约,我将其设为两个函数。

def compute_wcog(Xt: float, Xd: float, Xw: float) -> float:
    return ((Xt**2 + Xw**2) / Xd)**2

def compute_no_wcog(Xd: float, Xs: float) -> float:
    return (Xs**2 / Xd)**2

如果你真的需要一个单一的 compute 函数和有效的多个接口,你可以使用 @overload 装饰器:

from typing import Literal, Optional, overload

@overload
def compute(use_wcog: Literal[True], Xt: float, Xd: float, Xw: float, Xs: None) -> float: ...

@overload
def compute(use_wcog: Literal[False], Xt: None, Xd: float, Xw: None, Xs: float) -> float: ...

def compute(
    use_wcog: bool = False,
    Xt: Optional[float] = None,
    Xd: Optional[float] = None,
    Xw: Optional[float] = None,
    Xs: Optional[float] = None,
):
    assert Xd is not None

    if use_wcog:
        assert Xt is not None and Xw is not None
        res = ((Xt**2 + Xw**2) / Xd)**2
    else:
        assert Xs is not None
        res = (Xs**2 / Xd)**2

    return res

compute(True, 1.0, 1.0, 1.0, None)    # works
compute(False, None, 1.0, None, 1.0)  # works
compute(True, None, 1.0, None, 1.0)   # error: No overload variant of "compute" matches argument types...

但这显然是更多的代码,并没有太多收获。 :)