# Python 异常处理

## 学习目标
- 理解异常的概念和异常层次结构
- 掌握 try/except/else/finally 的用法
- 学会捕获和处理多种异常
- 理解异常的传播机制

---

## 1. 什么是异常

**异常（Exception）**是程序执行期间发生的错误，会中断正常的程序流程。

```python
# 常见的异常
print(1 / 0)           # ZeroDivisionError
print(int("abc"))      # ValueError
print([1, 2][10])      # IndexError
print({"a": 1}["b"])   # KeyError
```

### 1.1 内置异常层次

```
BaseException
 ├── SystemExit          # sys.exit() 引发
 ├── KeyboardInterrupt   # Ctrl+C 引发
 └── Exception           # 常规异常的基类
      ├── ArithmeticError
      │    └── ZeroDivisionError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── TypeError
      ├── ValueError
      ├── AttributeError
      ├── NameError
      │    └── UnboundLocalError
      ├── RuntimeError
      ├── OSError
      │    ├── FileNotFoundError
      │    └── PermissionError
      └── ...
```

---

## 2. try/except 基础

### 2.1 基本结构

```python
try:
    result = 10 / 0
except ZeroDivisionError:
    print("不能除以零！")

print("程序继续执行")
```

### 2.2 捕获多种异常

```python
def divide(a, b):
    try:
        result = a / b
        return result
    except ZeroDivisionError:
        print("错误：除数不能为零")
        return None
    except TypeError:
        print("错误：参数必须是数字")
        return None

# 测试
print(divide(10, 2))   # 5.0
print(divide(10, 0))   # 错误：除数不能为零
print(divide(10, "a")) # 错误：参数必须是数字
```

### 2.3 捕获异常对象

```python
try:
    num = int("abc")
except ValueError as e:
    print(f"捕获到异常: {e}")
    print(f"异常类型: {type(e).__name__}")
```

### 2.4 元组捕获多种异常

```python
try:
    # 可能引发 ValueError 或 TypeError
    value = int(input("请输入数字: "))
    result = 100 / value
except (ValueError, TypeError) as e:
    print(f"输入错误: {e}")
except ZeroDivisionError:
    print("不能输入零")
```

---

## 3. else 和 finally

### 3.1 else 子句

`else` 在 **没有异常时** 执行。

```python
try:
    result = 10 / 2
except ZeroDivisionError:
    print("除以零错误")
else:
    print(f"计算成功: {result}")  # 这行会执行
```

### 3.2 finally 子句

`finally` **无论是否发生异常** 都会执行，常用于清理资源。

```python
def read_file(filename):
    file = None
    try:
        file = open(filename, 'r')
        return file.read()
    except FileNotFoundError:
        print(f"文件不存在: {filename}")
        return ""
    finally:
        print("清理资源...")
        if file:
            file.close()
            print("文件已关闭")

# 测试
content = read_file('不存在的文件.txt')
```

### 3.3 完整结构

```python
try:
    print("尝试执行...")
    result = 10 / 2
except ZeroDivisionError:
    print("捕获异常")
else:
    print(f"成功: {result}")
finally:
    print("总是执行")

# 输出：
# 尝试执行...
# 成功: 5.0
# 总是执行
```

---

## 4. 异常传播

### 4.1 异常向上传播

```python
def func_c():
    print("func_c 开始")
    raise ValueError("C 出错了")

def func_b():
    print("func_b 开始")
    func_c()
    print("func_b 结束")  # 不会执行

def func_a():
    print("func_a 开始")
    try:
        func_b()
    except ValueError as e:
        print(f"func_a 捕获: {e}")
    print("func_a 结束")

func_a()
```

输出：
```
func_a 开始
func_b 开始
func_c 开始
func_a 捕获: C 出错了
func_a 结束
```

### 4.2 重新抛出异常

```python
def process_data(data):
    try:
        result = int(data) / 10
    except ValueError:
        print("数据格式错误，记录日志...")
        raise  # 重新抛出原始异常
    return result

try:
    process_data("abc")
except ValueError as e:
    print(f"最终捕获: {e}")
```

### 4.3 抛出新异常

```python
def divide_safe(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        raise ValueError(f"除数不能为零，收到: {b}")

try:
    divide_safe(10, 0)
except ValueError as e:
    print(f"错误: {e}")
```

### 4.4 保留异常链

```python
def convert_and_divide(data, divisor):
    try:
        number = int(data)
    except ValueError as e:
        raise ValueError(f"无法转换: {data}") from e
    
    try:
        return number / divisor
    except ZeroDivisionError as e:
        raise ValueError(f"除数不能为零") from e

try:
    convert_and_divide("abc", 0)
except ValueError as e:
    print(f"错误: {e}")
    if e.__cause__:
        print(f"原因: {e.__cause__}")
```

---

## 5. 主动抛出异常

### 5.1 raise 语句

```python
def set_age(age):
    if age < 0:
        raise ValueError("年龄不能为负数")
    if age > 150:
        raise ValueError("年龄不合理")
    return age

try:
    set_age(-5)
except ValueError as e:
    print(f"错误: {e}")
```

### 5.2 断言 assert

```python
def divide(a, b):
    assert b != 0, "除数不能为零"  # 条件为 False 时抛出 AssertionError
    return a / b

# 生产环境通常关闭断言: python -O script.py
# divide(10, 0)  # AssertionError
```

---

## 6. 最佳实践

### 6.1 具体捕获，不要裸 except

```python
# 不推荐 - 捕获所有异常（包括 SystemExit、KeyboardInterrupt）
try:
    do_something()
except:  # 捕获 BaseException
    pass

# 推荐 - 捕获具体异常
try:
    do_something()
except ValueError as e:
    print(f"值错误: {e}")
except Exception as e:
    print(f"其他错误: {e}")
```

### 6.2 不要吞掉异常

```python
# 不推荐 - 静默忽略异常
try:
    risky_operation()
except Exception:
    pass  # 错误被隐藏了！

# 推荐 - 至少记录日志
try:
    risky_operation()
except Exception as e:
    print(f"操作失败: {e}")
    # 或: logging.error(f"操作失败: {e}")
```

### 6.3 使用上下文管理器处理资源

```python
# 推荐 - 使用 with 语句
with open('file.txt', 'r') as f:
    content = f.read()

# 而不是
try:
    f = open('file.txt', 'r')
    content = f.read()
finally:
    f.close()
```

---

## 本节小结

- **异常**：程序执行中的错误，使用 `try/except` 捕获处理
- **结构**：`try` -> `except` -> `else`（无异常时）-> `finally`（总是执行）
- **传播**：未捕获的异常会向上传播，直到被处理或程序终止
- **重新抛出**：使用裸 `raise` 或 `raise ... from` 保留异常链
- **最佳实践**：捕获具体异常、不要静默吞掉、善用 `with` 语句

---

## 练习

1. 编写一个函数，安全地将字符串转换为整数，失败时返回默认值
2. 创建一个文件读取函数，处理所有可能的文件异常
3. 实现一个嵌套调用链，在最外层统一捕获并记录异常
4. 编写一个装饰器，自动捕获函数异常并打印友好错误信息

