如何用更简洁的方式让Mypy认可嵌套可选字段的非空断言?
嵌套可选字段的Mypy类型检查优化
问题场景
假设我们有如下嵌套类结构,其中包含多层可选字段:
class C: def __init__(self, number: int) -> None: self.number = number class B: def __init__(self, c: C | None) -> None: self.c = c class A: def __init__(self, b: B | None) -> None: self.b = b
当我们明确知道某个A实例的a.b和a.b.c都不为None时,直接访问a.b.c.number会触发Mypy报错:
# 此处对象保证不会出现None值 a = A(B(C(1337))) print(a.b.c.number) # Mypy报错: # Item "None" of "B | None" has no attribute "c" # Item "None" of "C | Any | None" has no attribute "number"
虽然可以用assert (a.b is not None) and (a.b.c is not None)解决,但嵌套层级深、字段名长时会非常繁琐。
简洁解决方案
1. 使用类型断言(cast)
通过typing.cast直接告诉Mypy当前变量的具体类型,适合临时场景:
from typing import cast a = A(B(C(1337))) # 逐层断言类型,可读性较好 b = cast(B, a.b) c = cast(C, b.c) print(c.number) # 或者链式简化(适合短场景) print(cast(C, cast(B, a.b).c).number)
2. 自定义类型守卫函数
如果需要多次检查相同的嵌套结构,可封装类型守卫函数,复用性更强:
from typing import TypeGuard def has_valid_b_and_c(a: A) -> TypeGuard[A]: return a.b is not None and a.b.c is not None a = A(B(C(1337))) if has_valid_b_and_c(a): # Mypy会在此代码块内自动推断a.b和a.b.c不为None print(a.b.c.number)
也可以针对单个类编写更通用的守卫:
def is_b_with_c(b: B | None) -> TypeGuard[B]: return b is not None and b.c is not None a = A(B(C(1337))) if is_b_with_c(a.b): print(a.b.c.number)
3. 调整类的类型注解(场景允许时)
如果在特定业务场景下,字段永远不会为None,可以直接修改类的初始化参数类型,从根源避免类型检查问题:
class B: # 此处c不再设为可选类型 def __init__(self, c: C) -> None: self.c = c class A: # 此处b不再设为可选类型 def __init__(self, b: B) -> None: self.b = b a = A(B(C(1337))) print(a.b.c.number) # Mypy不再报错
4. 拆分assert提升可读性
如果不想引入额外工具,也可以将长断言拆分为多行,兼顾简洁性和可读性:
a = A(B(C(1337))) assert a.b is not None assert a.b.c is not None print(a.b.c.number)
内容的提问来源于stack exchange,提问作者Erlend Magnus Viggen
相关产品推荐
相关产品推荐

