跳转至

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"
        }
    ]
}

调试操作

  1. 设置断点: 点击代码行号左侧
  2. 启动调试: F5 或点击调试按钮
  3. 单步执行: F10(跳过函数)/ F11(进入函数)
  4. 查看变量: 在左侧变量面板查看
  5. 监视表达式: 添加需要监视的变量

⚠️ 常见错误类型

1. SyntaxError - 语法错误

Python
# ❌ 错误
if x > 5
    print("x is greater than 5")

# ✅ 正确
if x > 5:
    print("x is greater than 5")

解决方法: 仔细检查冒号、括号、缩进


2. NameError - 名称错误

Python
# ❌ 错误
print(undefined_variable)

# ✅ 正确
variable = "defined"
print(variable)

解决方法: 检查变量是否已定义,注意作用域


3. TypeError - 类型错误

Python
# ❌ 错误
result = "10" + 5  # 字符串 + 整数

# ✅ 正确
result = int("10") + 5
# 或
result = "10" + str(5)

解决方法: 使用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 - 属性错误

Python
# ❌ 错误
my_list = [1, 2, 3]
my_list.push(4)  # 列表没有push方法

# ✅ 正确
my_list.append(4)

解决方法: 使用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时,按以下步骤排查:

  1. 复现问题 - 能稳定复现吗?
  2. 理解错误 - 错误信息是什么?
  3. 定位代码 - 哪一行出错?
  4. 检查输入 - 输入数据是什么?
  5. 检查变量 - 中间变量的值正确吗?
  6. 简化问题 - 能简化代码复现吗?
  7. 查阅文档 - API使用正确吗?
  8. 搜索解决方案 - 别人遇到过类似问题吗?

📝 练习

练习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 - 单元测试