python报错处理

Python 报错处理(Exception Handling)完整教程#

目标
通过本教程,读者能够:

  1. 明白 Python 报错(异常)的基本概念与产生机制。
  2. 熟练使用 try/except/finally、自定义异常、日志记录等三种常见的错误处理方案。
  3. 能够快速定位错误、避免程序崩溃,并把错误信息写入日志或展示给用户。

一、原因分析#

错误类型 典型场景 产生原因 典型错误信息
SyntaxError 代码写法错误 语法不符合 Python 语法 invalid syntax
ImportError 模块/包导入失败 模块不存在、路径错误 No module named 'xxx'
NameError 未定义变量 变量未声明或拼写错误 name 'foo' is not defined
TypeError 参数类型不匹配 传入了错误类型的参数 unsupported operand type(s)
ValueError 参数值错误 传入了无效但类型正确的值 invalid literal for int() with base 10
IOError/OSError 文件/网络操作 文件不存在、权限不足、网络异常 FileNotFoundError, ConnectionError
KeyError 访问字典不存在的 key key 不在字典里 KeyError: 'foo'
IndexError 列表/元组越界 访问了不存在的索引 list index out of range

注意:所有异常最终都会生成一个“Traceback”,它记录了异常发生的调用栈,便于定位问题。


二、三种分步骤的解决方案#

下面分别介绍三种常用的错误处理方法。每个方法都配有步骤代码示例,可按需选择。

1️⃣ 方案一:基础 try/except/finally#

适用于大多数场景,可以捕获并处理错误,保证程序继续执行。

步骤#

  1. 识别可能抛异常的代码段(如 I/O、网络请求、第三方库调用等)。
  2. 使用 try: 包装该段代码。
  3. except ExceptionType: 捕获具体异常,或用 except Exception: 捕获所有异常。
  4. except 块中记录错误给用户友好提示
  5. (可选)使用 finally: 执行无论是否异常都需要执行的清理代码(如关闭文件、释放资源)。

代码示例#

import os

def read_file(path):
    try:
        with open(path, 'r') as f:
            data = f.read()
        return data
    except FileNotFoundError as e:
        print(f"文件未找到: {path}")
        # 记录日志
        # logging.error(e)
    except PermissionError:
        print("没有足够的权限读取文件")
    finally:
        print("文件读取尝试完成")

# 调用
content = read_file("non_existent.txt")

要点

  • 不要捕获所有异常 (except:),否则会掩盖隐藏的 bug。
  • finally: 用来保证资源释放。

2️⃣ 方案二:使用 日志 记录错误#

当程序需要持续运行、或错误需要持久化记录(如后台服务),建议将错误写入日志文件。

步骤#

  1. 配置 logging 模块(文件、级别、格式)。
  2. except 块中使用 logging.exception()logging.error() 记录异常。
  3. (可选)根据业务需求,决定是否继续执行或抛出自定义异常。

代码示例#

import logging
import json

logging.basicConfig(
    filename='app.log',
    level=logging.ERROR,
    format='%(asctime)s %(levelname)s %(message)s'
)

def load_config(path):
    try:
        with open(path, 'r') as f:
            return json.load(f)
    except json.JSONDecodeError as e:
        logging.exception(f"配置文件解析错误: {path}")
        # 重新抛出,或者返回默认配置
        raise
    except FileNotFoundError:
        logging.error(f"配置文件不存在: {path}")
        return {}

config = load_config('config.json')

要点

  • logging.exception() 会自动把 traceback 信息写入日志。
  • 结合 logging 的不同级别(DEBUG/INFO/WARNING/ERROR/CRITICAL)可以细化日志策略。

3️⃣ 方案三:自定义异常层级化错误处理#

对于复杂项目,最好定义自己的异常类,方便调用者根据业务层次做不同处理。

步骤#

  1. 定义自定义异常类,继承自 Exception 或更细粒度的基类。
  2. 在业务函数内部抛出该异常,并附带有意义的错误信息。
  3. 在高层调用处捕获自定义异常,决定是否重试、提示用户或最终退出。

代码示例#

# my_exceptions.py
class ValidationError(Exception):
    """数据校验错误"""
    pass

class ServiceError(Exception):
    """后端服务异常"""
    pass


# business.py
from my_exceptions import ValidationError, ServiceError
import requests

def fetch_user(user_id: int) -> dict:
    if not isinstance(user_id, int):
        raise ValidationError("用户 ID 必须是整数")
    try:
        resp = requests.get(f"https://api.example.com/user/{user_id}")
        resp.raise_for_status()
        return resp.json()
    except requests.RequestException as err:
        raise ServiceError(f"获取用户失败: {err}") from err


# main.py
from business import fetch_user
from my_exceptions import ValidationError, ServiceError

def main():
    try:
        user = fetch_user("abc")   # 故意传错类型
    except ValidationError as ve:
        print(f"验证错误: {ve}")
    except ServiceError as se:
        print(f"服务错误: {se}")
    else:
        print(user)

if __name__ == "__main__":
    main()

要点

  • 使用 raise ... from ... 链接原始异常,保持 traceback。
  • 自定义异常可以携带更多上下文信息,方便日志分析。

三、常见问题解答(FAQ)#

# 问题 解决方案
1 异常捕获后程序仍然崩溃 检查是否在 except 后又 raise 了同样的异常,或没有 except 代码块覆盖到。
2 日志文件没有写入 确认 logging.basicConfig()filename 路径可写,或检查文件权限。
3 捕获 Exception 后仍然看到原始 Traceback 可能在 except 里又 raise 了异常。若想完全隐藏,请使用 except Exception: 并不再 re‑raise。
4 自定义异常未被捕获 确认 except CustomError: 与自定义异常类的命名、导入路径一致。
5 无法在 finally 中访问异常对象 except 里可将异常对象赋值给外部变量,或使用 sys.exc_info()
6 多线程/异步程序中的异常 线程内部异常不会自动传播到主线程;需要使用 concurrent.futuresfuture.exception() 或在 async 函数中 try/except
7 全局异常处理 在入口文件使用 sys.excepthooklogginglogging.basicConfig 统一捕获未处理异常。
8 异常堆栈过长导致日志文件膨胀 在生产环境中可使用 logging.handlers.RotatingFileHandlerTimedRotatingFileHandler,并限制日志级别。
9 如何在单元测试中验证异常 使用 pytest.raisesunittest.assertRaises
10 异常信息中包含敏感数据 在捕获后,先“清洗”异常信息再写日志或返回给用户。

四、总结#

  1. 先定位,再处理:先看错误信息、traceback,确定触发点。
  2. 层级化错误处理:从底层抛出自定义异常,再由业务层解码处理。
  3. 日志是最好的伙伴:无论是开发还是运维,持久化错误堆栈是排查问题的关键。
  4. 避免“吞噬”异常:不要随意捕获所有异常;至少要记录后再抛出或给出明确提示。

掌握了上述三种方案,你就能在大多数 Python 项目中优雅地处理错误,并保持代码的可维护性与健壮性。祝你编码愉快 🚀