Python 异常处理实战:从 try/except 到自定义异常

举报
yd_269685704 发表于 2026/11/05 10:05:04 2026/11/05
【摘要】 Python 异常处理实战:从 try/except 到自定义异常写 Python 时,程序崩溃往往不是因为逻辑写错,而是因为没料到的输入、网络抖动或者文件不存在。异常处理的目的不是“让程序永远不报错”,而是让错误在可控的地方发生、可读地记录、可恢复地处理。一、先理解:异常是流程,不是事故Python 奉行 EAFP(Easier to Ask for Forgiveness than Pe...

Python 异常处理实战:从 try/except 到自定义异常

写 Python 时,程序崩溃往往不是因为逻辑写错,而是因为没料到的输入、网络抖动或者文件不存在。异常处理的目的不是“让程序永远不报错”,而是让错误在可控的地方发生、可读地记录、可恢复地处理。

一、先理解:异常是流程,不是事故

Python 奉行 EAFP(Easier to Ask for Forgiveness than Permission):先尝试做,失败了再处理,而不是事先把所有条件检查一遍。

# LBYL:先检查,仍有竞态风险
import os
if os.path.exists("data.txt"):
    with open("data.txt") as f:
        data = f.read()

# EAFP:直接尝试,捕获明确的异常
try:
    with open("data.txt") as f:
        data = f.read()
except FileNotFoundError:
    data = ""

第二种写法更简洁,而且避免了“检查时存在、打开时消失”的竞态问题。

二、try/except/else/finally 的完整形态

四个子句各有分工,记住一句话:except 处理失败,else 只在成功时跑,finally 无论如何都跑。

def load_config(path):
    try:
        with open(path) as f:
            config = f.read()
    except FileNotFoundError:
        print("配置文件不存在,使用默认配置")
        return {}
    except PermissionError:
        print("没有读取权限")
        raise
    else:
        print("配置读取成功")
        return config
    finally:
        print("load_config 执行完毕")

注意两个细节:一是 except 的顺序要先具体后宽泛,把 FileNotFoundError 写在 Exception 前面,否则具体分支永远走不到;二是 else 里不要放可能抛异常的逻辑,否则异常会被误认为是 try 块里的失败。

三、不要写 bare except,也不要静默吞异常

# 反例:连 KeyboardInterrupt、SystemExit 也会被吞掉
try:
    do_something()
except:
    pass

# 正例:只捕获你知道如何处理的异常,并留下记录
import logging
try:
    do_something()
except ValueError as e:
    logging.warning("输入不合法:%s", e)

如果确实需要兜底,至少写 except Exception,并且把异常信息记下来。生产环境里“悄悄失败”比“大声崩溃”更难排查。

四、抛出异常时保留上下文:raise from

在封装底层异常时,用 raise ... from ... 显式建立异常链,排查时能同时看到原始原因和业务语义。

class DataLoadError(Exception):
    """数据加载失败"""

def load_users(path):
    try:
        return read_json(path)
    except (OSError, ValueError) as e:
        raise DataLoadError(f"无法加载用户数据:{path}") from e

调用方看到 DataLoadError 就知道是业务层面的问题,展开 __cause__ 又能看到原始的 OSError 或 ValueError。

五、自定义异常:让错误会说话

自定义异常不需要很复杂,关键是按“调用方如何处理”来划分,而不是按“哪里出错”来划分。

class PaymentError(Exception):
    pass

class InsufficientBalanceError(PaymentError):
    pass

class PaymentTimeoutError(PaymentError):
    pass

def pay(amount, balance):
    if amount > balance:
        raise InsufficientBalanceError("余额不足")
    # ... 调用支付网关,超时则 raise PaymentTimeoutError

这样调用方可以只捕获 PaymentError 做统一降级,也可以针对余额不足单独提示用户充值。好的异常层级,就是一份可执行的错误文档。

六、with 语句与异常的配合

with 能保证资源释放,即使块内抛异常也会先执行清理逻辑。标准库的 contextlib.suppress 则适合“这个异常真的可以忽略”的场景:

from contextlib import suppress

# 缓存文件删不掉也无所谓,不必为此写四行 try/except
with suppress(FileNotFoundError):
    os.remove("cache.tmp")

但 suppress 只适合你确定可以忽略的异常类型,不要用它包住大段逻辑。

七、自查清单

1. 捕获的是具体异常,而不是 except: 或 except Exception: pass。

2. 异常分支里有日志或明确的降级行为,而不是空着。

3. except 顺序先具体后宽泛,else 只放成功路径。

4. 对外抛出的异常带有业务语义,底层原因用 from 保留。

5. 资源清理交给 with/finally,不要依赖调用方记得关文件、关连接。

异常处理写得好,程序不是“不出错”,而是“出错时依然体面”。把每一次异常都当成一次沟通:告诉调用方发生了什么、严不严重、接下来该怎么办,这就是异常处理的全部要义。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。