异常处理与错误调试

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


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

在程序运行过程中,错误是不可避免的:文件可能不存在、网络可能超时、用户输入可能无效。如果没有妥善处理,程序会直接崩溃。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. 记录日志 - 生产环境一定要记录异常堆栈
相关推荐
笨鸟先飞,勤能补拙1 天前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能1 天前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂1 天前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study1 天前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境1 天前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境1 天前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1231 天前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102161 天前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb001 天前
一个项目带你入门AI应用开发01
python
0566461 天前
Python康复训练——常用标准库
开发语言·python·学习