# Python 调试技巧与性能分析

**学习目标**：掌握 Python 调试的多种方法，学会使用性能分析工具定位瓶颈，理解常见性能优化策略。

---

## 1. print 调试法

最简单但有效的调试方式，适合快速定位问题。

### 1.1 基础 print

```python
def calculate_price(items):
    total = 0
    for item in items:
        print(f"处理: {item['name']}, 价格: {item['price']}")
        total += item['price']
        print(f"  累计: {total}")
    print(f"最终总价: {total}")
    return total

items = [
    {"name": "苹果", "price": 5},
    {"name": "香蕉", "price": 3},
    {"name": "橙子", "price": 4}
]
calculate_price(items)
```

### 1.2 使用 f-string 调试（Python 3.8+）

```python
x = 42
name = "张三"
numbers = [1, 2, 3]

# = 后自动显示变量名和值
print(f"{x=}")        # x=42
print(f"{name=}")     # name='张三'
print(f"{numbers=}")  # numbers=[1, 2, 3]

# 表达式也支持
print(f"{sum(numbers)=}")  # sum(numbers)=6
print(f"{x * 2=}")         # x * 2=84
```

---

## 2. logging 模块调试

比 print 更灵活，支持级别控制和输出到文件。

```python
import logging

# 配置
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logger = logging.getLogger(__name__)

def process_order(order_id, items):
    logger.debug(f"开始处理订单 {order_id}")
    logger.debug(f"订单商品: {items}")
    
    try:
        total = sum(item['price'] for item in items)
        logger.info(f"订单 {order_id} 总价: {total}")
        
        if total > 1000:
            logger.warning(f"订单 {order_id} 金额较大: {total}")
        
        return total
    except KeyError as e:
        logger.error(f"订单 {order_id} 数据异常: 缺少字段 {e}")
        return None
    except Exception as e:
        logger.critical(f"订单 {order_id} 处理失败: {e}", exc_info=True)
        return None

# 测试
process_order(1, [{"name": "A", "price": 100}])
process_order(2, [{"name": "B"}])  # 缺少 price
```

---

## 3. pdb 调试器

### 3.1 交互式调试

```python
import pdb

def buggy_function(data):
    result = []
    for i, item in enumerate(data):
        # 设置断点
        pdb.set_trace()
        
        processed = item * 2
        result.append(processed)
    return result

# 运行后进入交互式调试
# buggy_function([1, 2, 3])
```

### 3.2 pdb 常用命令

```
(pdb) 命令一览

导航:
  n / next       ← 执行下一行（不进入函数）
  s / step       ← 执行下一行（进入函数）
  c / continue   ← 继续执行到下一个断点
  r / return     ← 执行到函数返回
  l / list       ← 显示当前代码上下文
  w / where      ← 显示调用栈

查看:
  p expr         ← 打印表达式值
  pp expr        ← 格式化打印
  a / args       ← 显示当前函数参数
  locals()       ← 显示所有局部变量

断点:
  b line         ← 在指定行设置断点
  b func         ← 在函数入口设置断点
  cl / clear     ← 清除断点
  tbreak         ← 临时断点（触发一次后删除）

其他:
  q / quit       ← 退出调试
  h / help       ← 帮助
  !command       ← 执行 Python 语句
```

### 3.3 breakpoint()（Python 3.7+）

```python
def complex_logic(numbers):
    total = 0
    for n in numbers:
        # 自动使用当前配置的调试器（默认 pdb）
        breakpoint()
        if n % 2 == 0:
            total += n
        else:
            total -= n
    return total

# 更优雅的方式：条件断点
def conditional_breakpoint(data):
    for i, item in enumerate(data):
        if item is None:  # 只在特定条件断
            breakpoint()
        process(item)
```

---

## 4. IDE 调试

### 4.1 VS Code 调试配置

```json
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: 当前文件",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": true
        },
        {
            "name": "Python: pytest",
            "type": "python",
            "request": "launch",
            "module": "pytest",
            "args": ["-v", "--pdb"],
            "console": "integratedTerminal"
        },
        {
            "name": "Python: Django",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/manage.py",
            "args": ["runserver"],
            "django": true
        }
    ]
}
```

### 4.2 调试技巧

```python
# 1. 条件断点（在 IDE 中右键断点设置条件）
for i in range(1000):
    result = process(i)
    # IDE 断点条件: i == 500

# 2. 日志断点（不暂停执行，只打印信息）
# VS Code: 右键断点 → Log Message

# 3. 异常断点
# 在异常发生时自动暂停
# VS Code: Breakpoints → Uncaught Exceptions
```

---

## 5. 性能分析

### 5.1 time 模块

```python
import time

# 基础计时
start = time.perf_counter()

# 执行代码
result = sum(range(1000000))

end = time.perf_counter()
print(f"耗时: {end - start:.6f} 秒")

# 多次测量取平均
def benchmark(func, *args, repeat=1000):
    times = []
    for _ in range(repeat):
        start = time.perf_counter()
        func(*args)
        times.append(time.perf_counter() - start)
    
    avg = sum(times) / len(times)
    min_t = min(times)
    max_t = max(times)
    print(f"平均: {avg*1000:.3f}ms, 最小: {min_t*1000:.3f}ms, 最大: {max_t*1000:.3f}ms")

benchmark(sum, range(10000))
benchmark(sum, list(range(10000)))
```

### 5.2 timeit 模块

```python
import timeit

# 测量代码片段
time1 = timeit.timeit("sum(range(100))", number=10000)
print(f"sum(range(100)): {time1:.4f}s")

time2 = timeit.timeit(
    "total = 0\nfor i in range(100): total += i",
    number=10000
)
print(f"手动循环: {time2:.4f}s")

# 命令行使用
# python -m timeit -s "x = list(range(100))" "sum(x)"
# python -m timeit -s "x = list(range(100))" "sum(x)" -n 100000 -r 5

# 对比不同实现
setup = "data = list(range(10000))"
tests = {
    "list comprehension": "[x*2 for x in data]",
    "map": "list(map(lambda x: x*2, data))",
    "for loop": """
result = []
for x in data:
    result.append(x*2)
""",
}

for name, code in tests.items():
    time = timeit.timeit(code, setup=setup, number=1000)
    print(f"{name:25s}: {time:.4f}s")
```

### 5.3 cProfile（性能分析器）

```python
import cProfile
import pstats

def slow_function():
    """模拟慢函数"""
    total = 0
    for i in range(1000000):
        total += i
    return total

def fast_function():
    """模拟快函数"""
    return sum(range(1000000))

def main():
    slow_function()
    fast_function()
    slow_function()

# 方式1：直接分析
cProfile.run("main()")

# 方式2：保存到文件
cProfile.run("main()", "profile_output.prof")

# 方式3：格式化输出
stats = pstats.Stats("profile_output.prof")
stats.sort_stats("cumulative")  # 按累计时间排序
stats.print_stats(10)  # 显示前 10 个

# 方式4：命令行
# python -m cProfile -s cumulative script.py
```

```
输出解读：
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3    0.150    0.050    0.150    0.050 script.py:5(slow_function)
        1    0.020    0.020    0.020    0.020 script.py:10(fast_function)

ncalls: 调用次数
tottime: 函数自身执行时间（不含子调用）
percall: 每次调用平均时间
cumtime: 累计时间（含子调用）
```

### 5.4 line_profiler（逐行分析）

```bash
# 安装
pip install line_profiler
```

```python
from line_profiler import LineProfiler

def process_data(data):
    result = []
    for item in data:
        squared = item ** 2
        result.append(squared)
    return sum(result)

# 创建分析器
lp = LineProfiler()
lp_wrapper = lp(process_data)

# 运行
lp_wrapper(list(range(10000)))

# 打印结果
lp.print_stats()

# 输出：
# Line #  Hits   Time  Per Hit  % Time  Line Contents
#      1                                  def process_data(data):
#      2     1      5      5.0      0.0      result = []
#      3  10001  30000      3.0     30.0      for item in data:
#      4  10000  40000      4.0     40.0          squared = item ** 2
#      5  10000  20000      2.0     20.0          result.append(squared)
#      6     1   5000   5000.0     10.0      return sum(result)
```

### 5.5 memory_profiler（内存分析）

```bash
pip install memory-profiler
```

```python
from memory_profiler import profile

@profile
def memory_heavy_function():
    """内存密集型函数"""
    # 大列表
    big_list = [i * 2 for i in range(1000000)]
    
    # 字典
    big_dict = {i: str(i) for i in range(100000)}
    
    # 处理
    total = sum(big_list)
    
    return total

memory_heavy_function()

# 输出：
# Line #    Mem usage    Increment  Occurrences   Line Contents
#      1     50.0 MiB     50.0 MiB           1   @profile
#      2     50.0 MiB      0.0 MiB           1   def memory_heavy_function():
#      3     65.5 MiB     15.5 MiB           1       big_list = [...]
#      4     73.2 MiB      7.7 MiB           1       big_dict = {...}
#      5     73.2 MiB      0.0 MiB           1       total = sum(big_list)
#      6     73.2 MiB      0.0 MiB           1       return total
```

---

## 6. 常见性能优化

### 6.1 选择合适的数据结构

```python
import timeit

# 1. 列表 vs 集合（查找）
setup = """
data_list = list(range(10000))
data_set = set(range(10000))
"""

# 列表查找 O(n)
t1 = timeit.timeit("9999 in data_list", setup=setup, number=10000)
# 集合查找 O(1)
t2 = timeit.timeit("9999 in data_set", setup=setup, number=10000)

print(f"列表查找: {t1:.4f}s")
print(f"集合查找: {t2:.4f}s")
# 列表查找通常比集合慢 100+ 倍

# 2. 字符串拼接
setup = "parts = ['hello'] * 100"

# + 拼接（慢）
t1 = timeit.timeit(
    "s = ''; [s := s + p for p in parts]",
    setup=setup, number=1000
)

# join（快）
t2 = timeit.timeit(
    "''.join(parts)",
    setup=setup, number=1000
)

print(f"+ 拼接: {t1:.4f}s")
print(f"join: {t2:.4f}s")
```

### 6.2 生成器 vs 列表

```python
import sys

# 列表：全部加载到内存
big_list = [x ** 2 for x in range(1000000)]
print(f"列表内存: {sys.getsizeof(big_list) / 1024 / 1024:.2f} MB")
# ~8 MB

# 生成器：惰性求值
big_gen = (x ** 2 for x in range(1000000))
print(f"生成器内存: {sys.getsizeof(big_gen)} bytes")
# ~200 bytes

# 使用场景
# 需要索引/多次遍历 → 列表
# 只需遍历一次/大数据 → 生成器
```

### 6.3 缓存

```python
from functools import lru_cache
import time

# 无缓存：重复计算
def fibonacci_slow(n):
    if n < 2:
        return n
    return fibonacci_slow(n - 1) + fibonacci_slow(n - 2)

# 有缓存：避免重复计算
@lru_cache(maxsize=128)
def fibonacci_fast(n):
    if n < 2:
        return n
    return fibonacci_fast(n - 1) + fibonacci_fast(n - 2)

# 性能对比
start = time.perf_counter()
fibonacci_slow(35)
print(f"无缓存: {time.perf_counter() - start:.4f}s")

start = time.perf_counter()
fibonacci_fast(35)
print(f"有缓存: {time.perf_counter() - start:.4f}s")
# 无缓存可能需要数秒，有缓存几乎瞬间
```

### 6.4 局部变量优化

```python
import timeit

# 全局变量访问较慢
GLOBAL_VALUE = 100

def use_global():
    total = 0
    for i in range(10000):
        total += i + GLOBAL_VALUE
    return total

# 局部变量访问更快
def use_local():
    local_value = 100  # 转为局部变量
    total = 0
    for i in range(10000):
        total += i + local_value
    return total

t1 = timeit.timeit(use_global, number=10000)
t2 = timeit.timeit(use_local, number=10000)
print(f"全局变量: {t1:.4f}s")
print(f"局部变量: {t2:.4f}s")
# 局部变量通常快 10-20%
```

---

## 7. 调试技巧总结

```
调试流程：
1. 复现问题 → 确定触发条件
2. 缩小范围 → 二分法注释代码
3. 添加日志 → print/logging
4. 断点调试 → pdb/IDE
5. 检查假设 → 验证变量值
6. 修复验证 → 编写测试

性能优化流程：
1. 测量 → cProfile/timeit
2. 定位瓶颈 → 找到最慢的部分
3. 优化 → 数据结构/算法/缓存
4. 验证 → 重新测量确认改善
5. 避免过早优化 → " premature optimization is the root of all evil "
```

---

## 8. 小结

| 工具 | 用途 | 命令 |
|------|------|------|
| print/f-string | 快速调试 | `f"{x=}"` |
| logging | 结构化日志 | `logging.basicConfig()` |
| pdb | 交互式调试 | `pdb.set_trace()` |
| breakpoint() | 现代断点 | `breakpoint()` |
| cProfile | 性能分析 | `cProfile.run()` |
| timeit | 微基准测试 | `timeit.timeit()` |
| line_profiler | 逐行分析 | `@profile` |
| memory_profiler | 内存分析 | `@profile` |

---

## 9. 练习题

1. 使用 pdb 调试一个有 Bug 的函数，找出问题所在。
2. 使用 cProfile 分析一段代码，找出最耗时的函数。
3. 对比列表和集合在不同大小下的查找性能。
4. 使用 `@lru_cache` 优化一个递归函数，测量优化效果。

---

> **下节预告**：我们将学习文档编写和注释规范，让你的代码更易于维护和团队协作。

