You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否在函数内执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 08:31:12