告别程序崩溃!Python异常处理的正确打开方式

在日常编程中,我们经常会遇到各种意外情况:文件找不到、网络连接突然断开、数据库查询出错......这些看似小问题却常常让我们的程序直接崩溃。那么如何正确处理这些异常,老板才不会熊你,这也算是技术活了。

为什么要异常处理不处理行不行?

不行,告诉你,小心老板把你开了,连N+1都拿不到。因为你想呀!你精心编写的程序因为一个微不足道的错误而全线崩溃,用户看着满屏的红色错误信息一头雾水。就这能让老板放心的把钱送到你的口袋吗?

那么良好的异常处理就像是给程序系上了安全带也给自己系上了安全带,不仅能避免"拿不到N+1",还能提供有意义的错误信息,让用户知道下一步该怎么做。

精准捕获:不要一杆子打

很多初学者会犯这样一个错误------不管什么异常,全都一网打尽:

python 复制代码
try:
    # 一堆代码
except:
    print("出错了!")

这种写法虽然能捕获所有异常,但却是个糟糕的做法。因为它连键盘中断(Ctrl+C)和程序退出请求都有可能会捕获,让你想强制停止程序都变得困难。

正确的做法是指定具体的异常类型:

python 复制代码
try:
    with open('data.txt', 'r') as f:
        content = f.read()
except FileNotFoundError:
    print("文件不存在,请检查路径")
except PermissionError:
    print("没有读取权限")

这样不仅能针对不同异常采取不同处理,还能让其他真正意外的异常暴露出来,便于调试。

异常处理的三层

第一层:直接处理

有些异常可以在发生时立即解决。比如文件不存在时创建新文件:

python 复制代码
try:
    config = load_config('config.json')
except FileNotFoundError:
    # 如果配置文件不存在,创建默认配置
    create_default_config()
    config = load_default_config()

第二层:转换异常

有时我们需要将底层异常转换为更高层的业务异常,让调用者不需要关心具体实现细节:

python 复制代码
def get_user_data(user_id):
    try:
        return db.query(User).get(user_id)
    except DatabaseError as e:
        # 将数据库异常转换为应用层异常
        raise DataRetrievalError(f"获取用户{user_id}数据失败") from e

使用from e保留原始异常信息,便于后续调试。

第三层:记录并重新抛出

有些异常在当前层面无法处理,但需要记录日志后再抛出:

python 复制代码
def process_data(data):
    try:
        return transform_data(data)
    except Exception as e:
        logger.error(f"处理数据时出错: {data}")
        raise  # 重新抛出异常,让上层处理

自定义异常:让错误信息更有意义

Python内置的异常已经很丰富了,但有时候创建自定义异常能让代码更清晰:

python 复制代码
class PaymentError(Exception):
    """支付相关异常的基类"""
    pass


class InsufficientBalanceError(PaymentError):
    """余额不足异常"""

    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"余额不足。当前余额:{balance},需要:{amount}")

使用自定义异常:

python 复制代码
def make_payment(data):
    user_balance = data["balance"]
    order_amount = data["amount"]
    if user_balance < order_amount:
        raise InsufficientBalanceError(user_balance, order_amount)

自定义异常的好处是提供了更明确的语义,调用方可以针对不同异常进行不同处理。

实用方式

1. 使用else和finally子句

python 复制代码
try:
    result = risky_operation()
except SomeSpecificError as e:
    handle_error(e)
else:
    # 如果没有发生异常,执行这里的代码
    process_result(result)
finally:
    # 无论是否发生异常,都会执行清理工作
    cleanup_resources()

2. 上下文管理器

对于资源管理,使用with语句比try-finally更加的酷炫:

python 复制代码
# 传统方式
try:
    f = open('file.txt', 'r')
    content = f.read()
finally:
    f.close()

# 更优雅的方式
with open('file.txt', 'r') as f:
    content = f.read()

3. 不要过度使用异常

异常处理虽然重要,但不应替代正常的流程控制。比如不要用异常来判断用户输入是否有效:

python 复制代码
# 不推荐:用异常做流程控制
try:
    age = int(user_input)
except ValueError:
    print("请输入数字")

# 推荐:先检查再转换
if user_input.isdigit():
    age = int(user_input)
else:
    print("请输入数字")

优雅的异常处理不是要把代码包装得密不透风,而是要有的放矢。记住这几个原则:

  • 只捕获你能处理的异常,让其他的异常抛出;
  • 异常信息要清晰有用,能帮助调用方理解问题
  • 自定义异常可以让你的代码更易理解和维护
  • 合理使用try/except/else/finally结构

这下以后再处理异常问题就能够更加的方便和流畅了,再也不怕老板熊你了!

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
默_笙1 天前
🍙 给每个请求过安检:FastAPI 是怎么把校验写进类型注解的
python
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
qq_426003961 天前
启动playwright录制codegen生成自动化测试脚本
python·自动化
虎头金猫1 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
胡写代码1 天前
别再前后端各写一套表单校验了
java·后端
长沙三为智能科技1 天前
家政小程序开发从0到上线:五阶段交付流程与验收清单
python
伞伞悦读1 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
大勇前进1 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端