稀有情况回退的 None 值类型提示

2 投票
1 回答
34 浏览
提问于 2025-04-14 16:02

为了避免输入错误,我经常会遇到同样的问题。

比如,我有一个函数 x,它很少返回值 None,大多数情况下返回的是 int


def x(i: int) -> Union[int, None]:
    if i == 0:
        return
    return i

def test(i: int):
    a = x(i)
    # typing issue: *= not supported for types int | None and int
    a *= 25

这个 x 在代码中使用得非常频繁,而且我已经检查过很多次,确认 x(i) 确实会返回 int 而不是 None

但是直接把它当作 int 使用时,会出现类型警告——比如说,你不能把可能是 None 的值进行乘法运算。

在这种情况下,最佳实践是什么呢?

我考虑过的想法有:

  1. if a is None: return 来检查 None 其实没有什么意义,因为这个情况已经是知道的了。
  2. a *= 25 # type: ignore 会让 a 的类型变成 Unknown
  3. a = x(i) # type: int 可以消除警告,但会产生一个新的警告:“int | None 不能被赋值给 int”。
  4. a = cast(int, x(i)),我还没有进行太多测试。

我通常会把 x 的返回类型改成只返回 int,在 return # type: ignore 中加上 ignore,并在文档字符串中提到它可能返回 None,这样可以避免整个代码库被类型警告污染。这是最好的方法吗?

def x(i: int) -> int:
    """might also return `None`"""
    if i == 0:
        return # type: ignore
    return i

1 个回答

5

这可能是一个例子,说明在某些情况下,抛出异常比用一个你根本不指望会被执行的返回语句要好。

def x(i: int) -> int:
    if i == 0:
        raise ValueError("didn't expect i==0")
    return i

def test(i: int):
    try:
        a = x(i)
    except ValueError:
        pass

    a *= 25

如果代码很有信心地验证了传入的参数x是有效的,那么可以省略try语句。

从静态分析的角度来看,这样说是对的:如果 x 返回了,那它肯定会返回一个int类型的值。(至于它是否会返回,这又是另一个问题。)


理想情况下,你可以定义一个叫NonZeroInt的类型,这样就可以把i == 0当作类型错误,而不是值错误。

# Made-up special form RefinementType obeys
#
#  isinstance(x, RefinementType[T, p]) == isinstance(x, T) and p(x)
NonZeroInt = RefinementType[int, lambda x: x != 0]

def x(i: NonZeroInt) -> int:
    return i

x(0)  # error: Argument 1 to "x" has incompatible type "int"; expected "NonZeroInt"  [arg-type]

i: int = 0
x(i)  # same error

j: NonZeroInt = 0  #  error: Incompatible types in assignment (expression has type "int", variable has type "NonZeroInt")  [assignment]

x(j)  # OK

k: NonZeroInt = 3  # OK
x(k)  # OK

撰写回答