Skip to content

1. 异常基础

1-1. 异常的概念

异常(Exception)是程序执行过程中出现的错误或意外情况。Python 使用异常处理机制来处理这些情况,而不是传统的错误码方式。

异常处理的主要优势:

  1. 错误隔离:将错误处理代码与正常业务逻辑分离
  2. 错误传播:异常可以沿着调用栈向上传播
  3. 错误恢复:提供错误恢复的机会
  4. 错误信息:提供详细的错误信息

1-2. 异常层次结构

Python 的异常体系是一个层次结构,所有异常都继承自 BaseException

BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
    ├── StopIteration
    ├── ArithmeticError
    │   ├── FloatingPointError
    │   ├── OverflowError
    │   └── ZeroDivisionError
    ├── AssertionError
    ├── AttributeError
    ├── BufferError
    ├── EOFError
    ├── ImportError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── MemoryError
    ├── NameError
    ├── OSError
    │   ├── FileNotFoundError
    │   ├── PermissionError
    │   └── TimeoutError
    ├── RuntimeError
    ├── SyntaxError
    ├── TypeError
    └── ValueError

1-3. 基本异常处理

python
try:
    # 可能引发异常的代码
    result = 10 / 0
except ZeroDivisionError:
    # 处理特定异常
    print("除数不能为零")
except Exception as e:
    # 处理其他异常
    print(f"发生错误: {e}")
else:
    # 没有异常时执行
    print("计算成功")
finally:
    # 无论是否发生异常都会执行
    print("清理工作")

2. 异常处理进阶

2-1. 自定义异常

python
class ValidationError(Exception):
    """数据验证错误"""
    def __init__(self, message, field=None):
        self.message = message
        self.field = field
        super().__init__(self.message)

    def __str__(self):
        if self.field:
            return f"{self.field}: {self.message}"
        return self.message

# 使用自定义异常
def validate_age(age):
    if age < 0:
        raise ValidationError("年龄不能为负数", "age")
    if age > 150:
        raise ValidationError("年龄不能超过150岁", "age")
    return age

try:
    validate_age(-1)
except ValidationError as e:
    print(e)  # 输出: age: 年龄不能为负数

2-2. 异常链

python
def process_data(data):
    try:
        return int(data)
    except ValueError as e:
        raise ValueError("数据处理失败") from e

try:
    process_data("abc")
except ValueError as e:
    print(f"错误: {e}")
    print(f"原因: {e.__cause__}")

2-3. 上下文管理器

python
class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        
    def __enter__(self):
        print(f"连接数据库: {self.db_name}")
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            print(f"发生错误: {exc_val}")
        print(f"关闭数据库连接: {self.db_name}")
        return True  # 抑制异常

# 使用上下文管理器
with DatabaseConnection("mydb") as db:
    print("执行数据库操作")
    raise ValueError("测试错误")

3. 异常处理最佳实践

3-1. 异常处理原则

  1. 具体异常优先
python
try:
    file = open("data.txt")
except FileNotFoundError:
    print("文件不存在")
except PermissionError:
    print("没有权限")
except Exception:
    print("其他错误")
  1. 避免空的 except 块
python
# 不好的做法
try:
    process_data()
except:
    pass

# 好的做法
try:
    process_data()
except Exception as e:
    logging.error(f"处理数据失败: {e}")
  1. 使用 finally 清理资源
python
file = None
try:
    file = open("data.txt")
    process_file(file)
except IOError as e:
    print(f"文件操作失败: {e}")
finally:
    if file:
        file.close()

3-2. 异常处理模式

  1. 重试机制
python
import time
from typing import Callable, Type, Tuple

def retry(
    func: Callable,
    exceptions: Tuple[Type[Exception], ...],
    max_attempts: int = 3,
    delay: float = 1.0
) -> Callable:
    def wrapper(*args, **kwargs):
        for attempt in range(max_attempts):
            try:
                return func(*args, **kwargs)
            except exceptions as e:
                if attempt == max_attempts - 1:
                    raise
                print(f"尝试 {attempt + 1} 失败: {e}")
                time.sleep(delay)
    return wrapper

@retry(exceptions=(ConnectionError, TimeoutError))
def fetch_data():
    # 模拟网络请求
    raise ConnectionError("连接失败")
  1. 异常转换
python
class DataProcessor:
    def process(self, data):
        try:
            return self._process_data(data)
        except ValueError as e:
            raise ProcessingError("数据处理失败") from e
        except TypeError as e:
            raise ProcessingError("数据类型错误") from e

    def _process_data(self, data):
        # 实际的数据处理逻辑
        pass
  1. 异常日志
python
import logging
import traceback

logging.basicConfig(level=logging.ERROR)

def process_with_logging(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logging.error(f"错误: {e}")
            logging.error(traceback.format_exc())
            raise
    return wrapper

4. 常见异常处理场景

4-1. 文件操作异常

python
def read_file(filename):
    try:
        with open(filename, 'r') as file:
            return file.read()
    except FileNotFoundError:
        print(f"文件 {filename} 不存在")
    except PermissionError:
        print(f"没有权限读取文件 {filename}")
    except IOError as e:
        print(f"读取文件时发生错误: {e}")

4-2. 网络请求异常

python
import requests
from requests.exceptions import RequestException

def fetch_url(url):
    try:
        response = requests.get(url, timeout=5)
        response.raise_for_status()
        return response.text
    except requests.Timeout:
        print("请求超时")
    except requests.ConnectionError:
        print("连接错误")
    except requests.HTTPError as e:
        print(f"HTTP错误: {e}")
    except RequestException as e:
        print(f"请求失败: {e}")

4-3. 数据库操作异常

python
import sqlite3

def query_database(query):
    conn = None
    try:
        conn = sqlite3.connect('database.db')
        cursor = conn.cursor()
        cursor.execute(query)
        return cursor.fetchall()
    except sqlite3.OperationalError as e:
        print(f"数据库操作错误: {e}")
    except sqlite3.IntegrityError as e:
        print(f"数据完整性错误: {e}")
    finally:
        if conn:
            conn.close()

5. 调试技巧

5-1. 异常信息获取

python
import sys
import traceback

try:
    # 可能出错的代码
    result = 1 / 0
except Exception as e:
    # 获取异常类型
    exc_type = type(e).__name__
    
    # 获取异常信息
    exc_message = str(e)
    
    # 获取异常堆栈
    exc_traceback = traceback.format_exc()
    
    # 获取异常发生的文件位置
    exc_file = sys.exc_info()[2].tb_frame.f_code.co_filename
    exc_line = sys.exc_info()[2].tb_lineno
    
    print(f"异常类型: {exc_type}")
    print(f"异常信息: {exc_message}")
    print(f"异常位置: {exc_file}:{exc_line}")
    print(f"堆栈跟踪:\n{exc_traceback}")

5-2. 断言调试

python
def calculate_discount(price, discount):
    assert price > 0, "价格必须大于0"
    assert 0 <= discount <= 1, "折扣必须在0到1之间"
    
    return price * (1 - discount)

# 使用断言
try:
    result = calculate_discount(-100, 0.2)
except AssertionError as e:
    print(f"断言失败: {e}")

6. 性能考虑

6-1. 异常处理开销

python
import timeit

def without_exception():
    result = []
    for i in range(1000):
        if i % 2 == 0:
            result.append(i)
    return result

def with_exception():
    result = []
    for i in range(1000):
        try:
            if i % 2 == 0:
                result.append(i)
        except:
            pass
    return result

# 测试性能
print("无异常处理:", timeit.timeit(without_exception, number=1000))
print("有异常处理:", timeit.timeit(with_exception, number=1000))

6-2. 异常处理优化

python
# 不好的做法
def process_data(data):
    try:
        return int(data)
    except:
        return None

# 好的做法
def process_data(data):
    if isinstance(data, (int, float)):
        return int(data)
    if isinstance(data, str) and data.isdigit():
        return int(data)
    return None

7. 实战示例

7-1. 数据验证器

python
from typing import Any, Dict, List
from dataclasses import dataclass

@dataclass
class ValidationResult:
    is_valid: bool
    errors: List[str]

class DataValidator:
    def __init__(self):
        self.rules = {}
        
    def add_rule(self, field: str, rule: callable, message: str):
        if field not in self.rules:
            self.rules[field] = []
        self.rules[field].append((rule, message))
        
    def validate(self, data: Dict[str, Any]) -> ValidationResult:
        errors = []
        for field, rules in self.rules.items():
            value = data.get(field)
            for rule, message in rules:
                try:
                    if not rule(value):
                        errors.append(f"{field}: {message}")
                except Exception as e:
                    errors.append(f"{field}: 验证失败 - {str(e)}")
        return ValidationResult(len(errors) == 0, errors)

# 使用示例
validator = DataValidator()
validator.add_rule("age", lambda x: x >= 0, "年龄不能为负数")
validator.add_rule("email", lambda x: "@" in x, "邮箱格式不正确")

data = {"age": -1, "email": "invalid"}
result = validator.validate(data)
if not result.is_valid:
    print("验证失败:")
    for error in result.errors:
        print(f"- {error}")

7-2. 重试装饰器

python
from functools import wraps
import time
import random
from typing import Type, Tuple, Callable, Any

class RetryError(Exception):
    """重试失败异常"""
    pass

def retry(
    exceptions: Tuple[Type[Exception], ...] = (Exception,),
    max_attempts: int = 3,
    delay: float = 1.0,
    backoff: float = 2.0,
    jitter: bool = True
) -> Callable:
    """
    重试装饰器
    
    Args:
        exceptions: 需要重试的异常类型
        max_attempts: 最大重试次数
        delay: 初始延迟时间(秒)
        backoff: 延迟时间增长因子
        jitter: 是否添加随机抖动
    """
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            current_delay = delay
            last_exception = None
            
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt == max_attempts - 1:
                        raise RetryError(f"重试 {max_attempts} 次后仍然失败") from e
                    
                    # 计算下一次延迟时间
                    if jitter:
                        current_delay = current_delay * backoff * (0.5 + random.random())
                    else:
                        current_delay = current_delay * backoff
                    
                    print(f"尝试 {attempt + 1} 失败: {e}")
                    print(f"等待 {current_delay:.2f} 秒后重试...")
                    time.sleep(current_delay)
            
            raise RetryError("重试失败") from last_exception
        return wrapper
    return decorator

# 使用示例
@retry(exceptions=(ConnectionError, TimeoutError), max_attempts=3)
def unreliable_function():
    if random.random() < 0.7:
        raise ConnectionError("连接失败")
    return "成功"

try:
    result = unreliable_function()
    print(f"结果: {result}")
except RetryError as e:
    print(f"最终失败: {e}")

TIP

异常处理最佳实践:

  1. 使用具体的异常类型而不是捕获所有异常
  2. 提供有意义的错误信息
  3. 适当使用异常链
  4. 清理资源使用 finally 或上下文管理器
  5. 考虑性能影响
  6. 记录异常信息
  7. 实现适当的重试机制