02 - 代码调试¶
学习时间: 1.5小时 重要性: ⭐⭐⭐⭐⭐ 高效找bug
🎯 学习目标¶
- 掌握多种调试技巧
- 学会使用pdb调试器
- 理解常见错误类型和解决方法
- 学会使用IDE调试功能
🐛 调试方法概览¶
方法1: Print调试(最简单)¶
Python
def process_data(data):
print(f"[DEBUG] 输入数据: {data}")
print(f"[DEBUG] 数据类型: {type(data)}")
result = data * 2
print(f"[DEBUG] 计算结果: {result}")
return result
优点: 简单直接,不需要学习成本 缺点: 代码混乱,生产环境需要删除
方法2: Logging调试(推荐)¶
Python
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def process_data(data):
logger.debug(f"输入数据: {data}")
logger.info("开始处理数据")
try:
result = data * 2
logger.debug(f"计算结果: {result}")
return result
except Exception as e:
logger.error(f"处理失败: {e}")
raise
日志级别: - DEBUG: 详细信息,仅用于诊断问题 - INFO: 确认程序按预期运行 - WARNING: 意外情况,但程序仍继续运行 - ERROR: 严重问题,某些功能无法执行 - CRITICAL: 严重错误,程序可能无法继续
方法3: PDB调试器(Python内置)¶
Python
import pdb
def complex_function(x, y):
result = x + y
pdb.set_trace() # 设置断点,程序会在这里暂停
result *= 2
return result
# 运行后进入pdb交互模式
# (Pdb) p result # 打印变量
# (Pdb) n # 执行下一行
# (Pdb) s # 进入函数
# (Pdb) c # 继续执行
# (Pdb) q # 退出
常用pdb命令:
| 命令 | 作用 |
|---|---|
p variable | 打印变量值 |
pp variable | 美化打印 |
n (next) | 执行下一行 |
s (step) | 进入函数 |
c (continue) | 继续执行到下一个断点 |
b line_num | 在指定行设置断点 |
l (list) | 显示当前代码 |
q (quit) | 退出调试器 |
方法4: 使用breakpoint()(Python 3.7+)¶
Python
def calculate(x, y):
result = x + y
breakpoint() # 自动进入调试器
result = result * 2
return result
# 可以通过环境变量指定调试器
# PYTHONBREAKPOINT=pdb python script.py
# PYTHONBREAKPOINT=0 python script.py # 禁用断点
🔍 IDE调试(VS Code示例)¶
配置launch.json¶
JSON
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
调试操作¶
- 设置断点: 点击代码行号左侧
- 启动调试: F5 或点击调试按钮
- 单步执行: F10(跳过函数)/ F11(进入函数)
- 查看变量: 在左侧变量面板查看
- 监视表达式: 添加需要监视的变量
⚠️ 常见错误类型¶
1. SyntaxError - 语法错误¶
解决方法: 仔细检查冒号、括号、缩进
2. NameError - 名称错误¶
解决方法: 检查变量是否已定义,注意作用域
3. TypeError - 类型错误¶
解决方法: 使用type()检查变量类型,必要时进行类型转换
4. IndexError - 索引错误¶
Python
# ❌ 错误
my_list = [1, 2, 3]
print(my_list[10]) # 索引超出范围
# ✅ 正确
if len(my_list) > 10:
print(my_list[10])
else:
print("索引超出范围")
解决方法: 检查列表长度,使用条件判断
5. KeyError - 键错误¶
Python
# ❌ 错误
my_dict = {"name": "张三"}
print(my_dict["age"]) # 键不存在
# ✅ 正确
print(my_dict.get("age", "未知"))
# 或
if "age" in my_dict:
print(my_dict["age"])
解决方法: 使用get()方法或先检查键是否存在
6. AttributeError - 属性错误¶
解决方法: 使用dir()查看对象可用方法,help()查看文档
7. FileNotFoundError - 文件未找到¶
Python
# ❌ 错误
with open("不存在的文件.txt", "r") as f:
content = f.read()
# ✅ 正确
from pathlib import Path
file_path = Path("不存在的文件.txt")
if file_path.exists():
content = file_path.read_text()
else:
print("文件不存在")
解决方法: 检查文件路径,使用exists()验证
🛠️ 调试技巧¶
技巧1: 二分法定位问题¶
Python
# 如果代码很长,用print确定问题范围
def long_function():
print("步骤1开始")
step1()
print("步骤1完成")
print("步骤2开始")
step2()
print("步骤2完成")
print("步骤3开始")
step3() # 如果这里出错,会打印"步骤3开始"但不打印"步骤3完成"
print("步骤3完成")
技巧2: 使用assert进行防御性编程¶
Python
def divide(a, b):
assert b != 0, "除数不能为0" # assert断言条件为真,否则抛出AssertionError
return a / b
def process_data(data):
assert isinstance(data, list), "data必须是列表"
assert len(data) > 0, "data不能为空列表"
return sum(data) / len(data)
技巧3: 异常捕获和堆栈跟踪¶
Python
import traceback
def risky_function():
try:
result = 1 / 0
except Exception as e:
print(f"错误: {e}")
traceback.print_exc() # 打印完整堆栈跟踪
risky_function()
技巧4: 使用上下文管理器调试¶
Python
from contextlib import contextmanager
import time
@contextmanager # @contextmanager用yield将普通函数变为上下文管理器
def debug_context(name):
print(f"[START] {name}")
start_time = time.time()
try:
yield
finally:
elapsed = time.time() - start_time
print(f"[END] {name} - 耗时: {elapsed:.2f}s")
# 使用
with debug_context("数据处理"):
process_large_dataset()
📝 调试清单¶
遇到bug时,按以下步骤排查:
- 复现问题 - 能稳定复现吗?
- 理解错误 - 错误信息是什么?
- 定位代码 - 哪一行出错?
- 检查输入 - 输入数据是什么?
- 检查变量 - 中间变量的值正确吗?
- 简化问题 - 能简化代码复现吗?
- 查阅文档 - API使用正确吗?
- 搜索解决方案 - 别人遇到过类似问题吗?
📝 练习¶
练习1: 修复以下代码¶
Python
def calculate_average(numbers):
total = sum(numbers)
average = total / len(numbers)
return average
# 测试
result = calculate_average([]) # 会出错,请修复
练习2: 使用pdb调试¶
Python
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
# 使用pdb调试factorial(5),观察每次递归的n值
练习3: 添加日志¶
Python
def process_file(filepath):
with open(filepath, 'r') as f:
data = f.read()
return data.upper()
# 为上述函数添加适当的日志记录
🎯 自我检查¶
- 能使用print和logging进行调试
- 会使用pdb设置断点和单步执行
- 理解常见错误类型及其解决方法
- 能快速定位和修复bug
- 会阅读和理解错误堆栈信息
📚 延伸阅读¶
下一步: 03 - 单元测试