提前返回前已做实例检查,Mypy仍报union-attr错误怎么办?
解决责任链模式中Mypy的Union类型属性访问错误
问题场景
在Python中实现责任链模式时,将包含可选字段和联合类型的Request对象传入处理器链。处理器的通用逻辑是在方法开头判断是否处理当前请求,不处理则提前返回并调用下一个处理器,但Mypy会捕获到联合类型属性访问的错误。
示例代码
from pathlib import Path from typing import Any, Callable class Request: data: int | Any | None = None location: str | Path | None = None def handle_int(request: Request, next_handler: Callable[[Request], int]) -> int: is_int = isinstance(request.data, int) # 注:原代码存在笔误,此处应为isinstance(request.location, str),否则逻辑不成立 is_str = isinstance(request.location, str) if not (is_int and is_str): return next_handler(request) a = request.location.split("_") return request.data * 3
Mypy报错信息
test_mypy.py:17:9: error: Item "Path" of "str | Path | None" has no attribute "split" [union-attr] test_mypy.py:17:9: error: Item "None" of "str | Path | None" has no attribute "split" [union-attr] test_mypy.py:19:12: error: Unsupported operand types for * ("None" and "int") [operator] test_mypy.py:19:12: note: Left operand is of type "int | Any | None"
需求:除了创建更具体的请求类型外,如何让Mypy识别提前返回后的代码已完成类型检查,避免重复断言的不良模式?
解决方案
1. 自定义类型守卫(推荐)
通过定义类型守卫函数封装类型检查逻辑,既避免代码重复,又能让Mypy正确推断后续代码的类型:
from pathlib import Path from typing import Any, Callable, TypeGuard class Request: data: int | Any | None = None location: str | Path | None = None def is_valid_int_request(request: Request) -> TypeGuard[Request]: """类型守卫:标记request的data为int且location为str""" return isinstance(request.data, int) and isinstance(request.location, str) def handle_int(request: Request, next_handler: Callable[[Request], int]) -> int: if not is_valid_int_request(request): return next_handler(request) # Mypy可自动推断:request.location是str,request.data是int a = request.location.split("_") return request.data * 3
类型守卫通过TypeGuard注解告诉Mypy:当函数返回True时,传入的Request对象已经满足指定的类型约束,后续代码无需重复检查。
2. 极简内联断言(次选)
如果不想单独定义守卫函数,可在提前返回后添加极简断言,利用Mypy的类型窄化特性,无需重复完整的谓词检查:
def handle_int(request: Request, next_handler: Callable[[Request], int]) -> int: is_int = isinstance(request.data, int) is_str = isinstance(request.location, str) if not (is_int and is_str): return next_handler(request) # Mypy通过断言窄化类型,运行时若关闭断言(默认)则不会执行检查 assert isinstance(request.location, str) assert isinstance(request.data, int) a = request.location.split("_") return request.data * 3
3. 强制类型转换(不推荐)
使用typing.cast绕过Mypy的类型检查,但会丢失类型安全保障,仅适合临时场景:
from typing import cast def handle_int(request: Request, next_handler: Callable[[Request], int]) -> int: is_int = isinstance(request.data, int) is_str = isinstance(request.location, str) if not (is_int and is_str): return next_handler(request) location_str = cast(str, request.location) data_int = cast(int, request.data) a = location_str.split("_") return data_int * 3
内容的提问来源于stack exchange,提问作者Martin O Leary
相关产品推荐
相关产品推荐

