异常处理与错误调试

📚 今日主题:异常处理与错误调试


一、为什么需要异常处理?

在程序运行过程中,错误是不可避免的:文件可能不存在、网络可能超时、用户输入可能无效。如果没有妥善处理,程序会直接崩溃。Python 的异常处理机制让我们能够优雅地应对错误,保证程序的健壮性。

ini 复制代码
# ❌ 没有异常处理 - 程序会崩溃
def read_file_bad(filename):
    f = open(filename, 'r')
    content = f.read()
    f.close()
    return content

# 如果文件不存在,直接抛出 FileNotFoundError
# read_file_bad('not_exist.txt')  # 程序终止!

二、try-except 基础用法

2.1 基本结构

python 复制代码
def read_file_safe(filename):
    try:
        f = open(filename, 'r', encoding='utf-8')
        content = f.read()
        f.close()
        return content
    except FileNotFoundError:
        print(f"错误:文件 '{filename}' 不存在")
        return None
    except PermissionError:
        print(f"错误:没有权限读取 '{filename}'")
        return None
    except Exception as e:
        print(f"发生未知错误:{e}")
        return None

# 测试
result = read_file_safe('not_exist.txt')
print(f"返回结果:{result}")  # 返回 None,程序继续运行

2.2 捕获多个异常

python 复制代码
def safe_divide(a, b):
    try:
        result = a / b
        return result
    except (ZeroDivisionError, TypeError) as e:
        print(f"计算错误:{e}")
        return None

print(safe_divide(10, 2))    # 5.0
print(safe_divide(10, 0))    # None,捕获 ZeroDivisionError
print(safe_divide(10, 'a'))  # None,捕获 TypeError

三、else 和 finally 子句

python 复制代码
def process_data(data):
    try:
        result = int(data)
        print(f"转换成功:{result}")
    except ValueError:
        print("转换失败:输入不是有效数字")
        return None
    else:
        # 没有异常时执行
        print("✓ 数据验证通过")
        return result * 2
    finally:
        # 无论是否异常都执行(常用于清理资源)
        print("--- 处理结束 ---")

process_data("42")
# 输出:
# 转换成功:42
# ✓ 数据验证通过
# --- 处理结束 ---

process_data("abc")
# 输出:
# 转换失败:输入不是有效数字
# --- 处理结束 ---

四、主动抛出异常

python 复制代码
def validate_age(age):
    if age < 0:
        raise ValueError("年龄不能为负数")
    if age > 150:
        raise ValueError("年龄不能超过 150 岁")
    if not isinstance(age, int):
        raise TypeError("年龄必须是整数")
    return f"年龄验证通过:{age}岁"

# 测试
try:
    print(validate_age(25))    # ✓ 年龄验证通过:25 岁
    print(validate_age(-5))    # 抛出 ValueError
except ValueError as e:
    print(f"验证失败:{e}")

五、自定义异常类

python 复制代码
class InsufficientFundsError(Exception):
    """余额不足异常"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"余额不足:当前余额 {balance},需要 {amount}")

class BankAccount:
    def __init__(self, initial_balance=0):
        self.balance = initial_balance
    
    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        return self.balance
    
    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("存款金额必须大于 0")
        self.balance += amount
        return self.balance

# 测试
account = BankAccount(100)
try:
    account.withdraw(150)  # 触发自定义异常
except InsufficientFundsError as e:
    print(f"取款失败:{e}")
    print(f"建议:请先存款至少 {e.amount - e.balance} 元")

六、实用的调试技巧

6.1 记录异常堆栈

python 复制代码
import traceback
import logging

# 配置日志
logging.basicConfig(
    level=logging.ERROR,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def risky_operation():
    try:
        # 模拟可能出错的操作
        data = [1, 2, 3]
        return data[10]  # IndexError
    except Exception as e:
        # 记录完整堆栈信息
        logging.error(f"操作失败:{e}")
        logging.error(traceback.format_exc())
        raise

# 调用后会看到完整的错误堆栈

6.2 上下文管理器处理资源

python 复制代码
from contextlib import contextmanager

@contextmanager
def open_file_safe(filename, mode='r'):
    f = None
    try:
        f = open(filename, mode, encoding='utf-8')
        yield f
    except Exception as e:
        print(f"文件操作出错:{e}")
        raise
    finally:
        if f:
            f.close()
            print("文件已关闭")

# 使用
with open_file_safe('test.txt', 'w') as f:
    f.write('Hello World')
# 自动关闭文件,即使发生异常

七、最佳实践总结

✅ 推荐做法 ❌ 避免做法
捕获具体异常类型 except: 捕获所有异常
使用 finally 清理资源 忘记关闭文件/连接
记录异常堆栈便于调试 静默吞掉异常
自定义异常表达业务错误 滥用通用 Exception
尽早抛出,晚些捕获 过度嵌套 try-except

八、实战练习

python 复制代码
# 练习:实现一个安全的 JSON 配置加载器
import json

def load_config(filepath):
    """
    安全加载 JSON 配置文件
    要求:
    1. 文件不存在时返回默认配置
    2. JSON 格式错误时记录日志并返回默认配置
    3. 成功时返回解析后的字典
    """
    default_config = {
        "debug": False,
        "max_connections": 100,
        "timeout": 30
    }
    
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            config = json.load(f)
            print(f"✓ 成功加载配置:{filepath}")
            return config
    except FileNotFoundError:
        print(f"⚠ 配置文件不存在,使用默认配置")
        return default_config
    except json.JSONDecodeError as e:
        print(f"⚠ JSON 格式错误:{e},使用默认配置")
        return default_config
    except Exception as e:
        print(f"⚠ 未知错误:{e},使用默认配置")
        return default_config

# 测试
config = load_config('config.json')
print(f"当前配置:{config}")

📝 今日要点

  1. 异常处理是健壮程序的基石 - 不要让用户看到崩溃的堆栈
  2. 精准捕获 - 只捕获你预期并能处理的异常
  3. 资源清理 - 用 finally 或上下文管理器确保资源释放
  4. 主动抛出 - 用异常表达业务逻辑错误
  5. 记录日志 - 生产环境一定要记录异常堆栈
相关推荐
Csvn1 小时前
Python 面向对象编程基础:类与对象
python
yy我不解释2 小时前
关于comfyui的mmaudio音频生成插件时时间不一致问题(四)(video upload)(解决方法)
开发语言·python·ai作画·音视频·comfyui
2301_776508722 小时前
用Python和Twilio构建短信通知系统
jvm·数据库·python
紫丁香2 小时前
pytest_自动化测试5
python·功能测试·单元测试·集成测试·pytest
姚青&2 小时前
Pytest fixture 参数化(params 参数)
开发语言·python·pytest
2301_793804692 小时前
深入理解Python的if __name__ == ‘__main__‘
jvm·数据库·python
qyzm2 小时前
牛客周赛 Round 136
数据结构·python·算法
紫檀香2 小时前
Alembic入门教程
后端·python
2401_833197732 小时前
用Python生成艺术:分形与算法绘图
jvm·数据库·python