断言与异常处理:assert 与 try-except 的博弈

说实话,很多刚入行的同学都会问我一个问题:「老师,assert 和 try-except 到底有什么区别?我该用哪个?」

这个问题问得好。我当年刚写 Python 的时候也纠结过。有一次线上服务半夜挂了,查了半天发现是 assert 把生产数据给拦了——嗯,从那以后我才真正搞明白这两个东西的定位。

今天咱们就把这事彻底说清楚。

核心区别:一个用于「不应该发生」,一个用于「可能发生」

先给个最直白的结论:

  • assert 是开发阶段的检查工具。它假设「这事不可能发生」。
  • try-except 是运行时的容错机制。它承认「这事确实有可能发生」。

你想想看,如果你写了一个函数,要求传入的参数必须是正整数。你觉得调用方一定会传对——那用 assert。但如果你在读取一个用户上传的文件,文件可能不存在、可能格式不对——那就得用 try-except。

一句话总结:

assert 是「防呆」的,try-except 是「防错」的。

assert 的正确打开方式

assert 的语法很简单:

assert 条件, "错误信息"

如果条件为 False,程序会直接抛出 AssertionError 并终止。

我个人习惯在以下场景用 assert:

  • 函数前置条件检查:比如参数不能为 None,列表不能为空
  • 函数后置条件检查:比如返回值必须在某个范围内
  • 单元测试中的断言:这是最标准的用法
  • 代码中「不可能到达」的分支:比如 switch-case 的 default 分支

举个例子:

def calculate_discount(price, rate):
    assert price > 0, "价格必须为正数"
    assert 0 < rate <= 1, "折扣率必须在 0 到 1 之间"
    
    result = price * rate
    assert result > 0, "计算结果异常"
    return result

我在项目中遇到过一个问题:有个同事在核心业务逻辑里用了 assert 来校验用户输入。结果测试环境一切正常,上了生产就崩了——因为生产环境默认会禁用 assert(python -O 参数)。

警告:

千万不要用 assert 来做数据校验!生产环境可能禁用 assert,你的校验逻辑会直接消失。

try-except 的正确打开方式

try-except 是用来处理「预期内」的异常情况的。说白了,就是你知道这里可能会出问题,而且你知道怎么处理。

我建议在以下场景用 try-except:

  • 外部资源访问:文件读写、网络请求、数据库操作
  • 用户输入处理:格式转换、类型转换
  • 第三方库调用:你不知道它会抛出什么异常
  • 业务逻辑中的可恢复错误:比如重试、降级、回滚

看个实际例子:

def load_user_config(user_id):
    try:
        with open(f"configs/{user_id}.json", "r") as f:
            return json.load(f)
    except FileNotFoundError:
        # 用户没有配置文件,返回默认配置
        return get_default_config()
    except json.JSONDecodeError:
        # 配置文件损坏,记录日志并返回默认配置
        logger.error(f"用户 {user_id} 的配置文件损坏")
        return get_default_config()
    except Exception as e:
        # 其他未知错误,记录日志并抛出
        logger.critical(f"加载用户配置时发生未知错误: {e}")
        raise

注意看,这里我用了三个不同的 except 分支。为什么?因为每个异常的处理方式不一样。

我曾经接手过一个项目,整个代码里只有一个 except Exception,所有错误都吞掉了。结果线上出了 bug,日志里全是「发生错误」,根本不知道哪里出了问题。嗯,那段时间我每天都在骂人。

小技巧:

尽量捕获具体的异常类型。不要用裸的 except: 或者 except Exception。你想想看,如果连键盘中断(KeyboardInterrupt)都被你吞了,用户想停都停不了。

什么时候该用哪个?一张表说清楚

场景 用 assert 用 try-except
函数参数校验(开发阶段) ✅ ❌
用户输入校验(生产环境) ❌ ✅
单元测试 ✅ ❌
文件读写 ❌ ✅
「不可能发生」的分支 ✅ ❌
网络请求重试 ❌ ✅
调试时检查中间结果 ✅ ❌
业务逻辑中的可恢复错误 ❌ ✅

避坑指南:我踩过的那些坑

我曾经在一个金融项目里,用 assert 来校验交易金额是否合法。结果上线后,运维同学用 -O 参数启动了服务,所有校验全部失效。那天晚上,我们回滚了三次。

我曾经看到有人这么写代码:

try:
    assert condition
except AssertionError:
    # 处理错误
    pass

这简直是双重错误。首先,assert 不应该被 try 捕获——它本身就是用来让程序崩溃的。其次,你捕获了 AssertionError 却什么都不做,那这个 assert 还有什么意义?

我曾经在一个遗留系统里看到,整个项目只有一个 try-except 块,包住了 main 函数。所有异常都被捕获,然后打印一句「系统错误」就完事了。这种写法,说白了就是掩耳盗铃。

核心逻辑图

下面这张图展示了 assert 和 try-except 在整个软件生命周期中的定位:

断言与异常处理的核心定位 开发阶段 使用 assert 检查前置/后置条件 单元测试断言 「不可能发生」的分支 运行阶段 使用 try-except 外部资源访问 用户输入处理 可恢复的业务错误 关键原则 1. assert 是开发工具,不是错误处理机制 2. try-except 是运行时容错,不是代码补丁 3. 不要用 assert 做数据校验(生产环境可能被禁用) 4. 不要用 try-except 捕获「不可能发生」的错误

最后说两句

其实 assert 和 try-except 没有谁好谁坏,关键是用对地方。我个人的经验是:

  • 写代码的时候,先用 assert 把「不可能」的情况卡死
  • 上线之前,把 assert 换成 try-except 来处理「可能」的情况
  • 如果某个地方既可以用 assert 又可以用 try-except,那大概率是设计有问题

记住一句话:assert 是写给开发者的,try-except 是写给用户的。

可可精品资料库