能否在函数内执行Mypy断言以影响原始类型参数?
问题描述
我有一个简单的验证函数:
def is_valid_build_target(target: Any, throw=False) -> bool: target = str(target) allowed_targets = ["dev", "prod"] is_allowed = target.lower() in allowed_targets if not is_allowed and throw: raise ValueError( f"Invalid target '{target}'. Must be one of: {allowed_targets}" ) assert target is not None return is_allowed
调用这个函数后,Mypy没法把函数内的断言传递到调用栈上层(推测是因为target是原始类型,函数内会生成本地化副本):
from typing import Literal, cast import os Target = Literal["dev", "prod"] target: Target | None = cast(Target | None, os.getenv("APP_TARGET", None)) if not is_valid_build_target(target): raise ValueError(f"Invalid target, I could have used throw=True, but I wanted a custom error message") # Mypy仍认为target可能为None
要是把函数逻辑内联,或者在调用后手动加assert target is not None,Mypy会认可,但这样就没法把运行时验证逻辑封装到独立函数里:
if not is_valid_build_target(target): raise ValueError(f"Invalid target...") assert target is not None # Mypy验证通过
有没有办法在验证函数内部完成校验,同时让Mypy验证通过?
解决方案
1. 用类型守卫(Type Guard)
Mypy支持typing.TypeGuard(Python 3.10+,旧版本可以用typing_extensions兼容),能让函数告诉类型检查器:返回True时,输入参数符合指定类型。
修改验证函数为类型守卫:
from typing import TypeGuard, Any, Literal Target = Literal["dev", "prod"] def is_valid_build_target(target: Any | None) -> TypeGuard[Target]: if target is None: return False target_str = str(target).lower() allowed_targets = {"dev", "prod"} return target_str in allowed_targets
调用时,Mypy会自动识别:函数返回True时,target类型会缩小为Target;返回False时进入异常分支,后续代码不会执行,也就不会再认为target可能为None:
import os target: Target | None = cast(Target | None, os.getenv("APP_TARGET", None)) if not is_valid_build_target(target): raise ValueError(f"Invalid target, must be one of: dev, prod") # Mypy现在认定target是Target类型,不再提示可能为None
2. 返回验证后的目标(而非布尔值)
换个思路,让验证函数直接返回校验后的目标值,验证失败就抛异常,成功则返回符合类型要求的目标:
from typing import Literal, Any, cast Target = Literal["dev", "prod"] def validate_build_target(target: Any | None) -> Target: if target is None: raise ValueError("Target cannot be None") target_str = str(target).lower() allowed_targets = {"dev", "prod"} if target_str not in allowed_targets: raise ValueError(f"Invalid target '{target}'. Must be one of: dev, prod") return cast(Target, target_str)
调用时直接赋值,Mypy会自动识别返回值的类型:
import os target: Target | None = cast(Target | None, os.getenv("APP_TARGET", None)) target = validate_build_target(target) # Mypy确认target是Target类型
3. 保留原函数结构,加类型重载注解
如果不想大幅修改原函数,可以给函数加@overload重载注解,明确不同输入下的类型推断逻辑:
from typing import overload, Any, Literal, TypeGuard Target = Literal["dev", "prod"] @overload def is_valid_build_target(target: Any | None, throw: Literal[True]) -> Target: ... @overload def is_valid_build_target(target: Any | None, throw: Literal[False] = ...) -> TypeGuard[Target]: ... def is_valid_build_target(target: Any | None, throw=False) -> bool | Target: target_str = str(target).lower() if target is not None else "" allowed_targets = {"dev", "prod"} is_allowed = target_str in allowed_targets if not is_allowed: if throw: raise ValueError(f"Invalid target '{target}'. Must be one of: {allowed_targets}") return False return cast(Target, target_str) if throw else True
这种方式能兼容原有的throw参数逻辑,同时让Mypy根据参数值推断返回类型或类型守卫效果。
内容的提问来源于stack exchange,提问作者brandonscript
相关产品推荐
相关产品推荐

