如何向类型检查器提示属性在特定场景下不为None?
解决mypy对可选类型的类型推断问题
我想避免在使用自定义类的代码里写大量if或assert语句,于是定义了TemplateRow这个NamedTuple,其中template和error都是可选类型,还加了valid属性来判断该行是否有效(即error为None)。read_template函数读取TSV文件并尝试转成Pydantic模型,成功就返回带template的TemplateRow,失败则返回带error的TemplateRow。但在下面的循环里:
ok: list[Template]=[] notok: list[str] = [] for row in read_template(file): if row.valid: ok.append(row.template) else: notok.append(row.error)
mypy会报错row.error可能为None,但实际上通过row.valid判断后,error不可能是None。怎么让类型检查器识别这个逻辑?
方法1:用受歧视的联合类型(推荐)
把TemplateRow拆成两个独立的NamedTuple,用一个固定的鉴别字段(比如type)区分有效/无效状态,让read_template返回这两个类型的联合。mypy能通过鉴别字段的判断自动推断对应类型:
from typing import NamedTuple, Union from pydantic import BaseModel class Template(BaseModel): # 你的Pydantic模型定义 pass class ValidTemplateRow(NamedTuple): type: str = "valid" template: Template class InvalidTemplateRow(NamedTuple): type: str = "invalid" error: str TemplateRow = Union[ValidTemplateRow, InvalidTemplateRow] def read_template(file) -> list[TemplateRow]: rows = [] # 读取TSV并转换的逻辑示例 for line in file: try: template = Template.parse_raw(line) rows.append(ValidTemplateRow(template=template)) except Exception as e: rows.append(InvalidTemplateRow(error=str(e))) return rows # 使用示例 ok: list[Template] = [] notok: list[str] = [] for row in read_template(file): if row.type == "valid": ok.append(row.template) else: notok.append(row.error)
这种方式结构清晰,mypy能完美识别分支类型,不会再报类型错误。
方法2:添加自定义类型守卫
如果不想拆分TemplateRow,可以写类型守卫函数,明确告诉mypyrow.valid的布尔值对应的实际类型:
from typing import NamedTuple, Optional, TypeGuard from pydantic import BaseModel class Template(BaseModel): pass class TemplateRow(NamedTuple): template: Optional[Template] = None error: Optional[str] = None @property def valid(self) -> bool: return self.error is None # 定义精准的类型守卫 def is_valid_row(row: TemplateRow) -> TypeGuard[tuple[Template, None]]: return row.valid def is_invalid_row(row: TemplateRow) -> TypeGuard[tuple[None, str]]: return not row.valid # 使用示例 ok: list[Template] = [] notok: list[str] = [] for row in read_template(file): if is_valid_row(row): ok.append(row.template) elif is_invalid_row(row): notok.append(row.error)
通过类型守卫,mypy能理解分支里的字段非空性,解决报错问题。
方法3:用assert快速解决(侵入性强)
如果不想改结构或加守卫,直接在else分支加assert语句,强制告诉mypyrow.error不为None:
ok: list[Template]=[] notok: list[str] = [] for row in read_template(file): if row.valid: ok.append(row.template) else: assert row.error is not None, "无效行必须包含错误信息" notok.append(row.error)
这种方式简单直接,但会在代码中加入运行时检查(可以通过Python的-O参数关闭),适合快速临时解决问题。
内容的提问来源于stack exchange,提问作者Wardy
相关产品推荐
相关产品推荐

