在前几篇中,我们的程序能处理数据、操作文件了,但有个隐藏的 “雷区”:只要用户输入不符合预期(比如让输入数字却输入了文字),或者文件操作出问题(比如文件被意外删除),程序就会直接报错崩溃。这在实际使用中是不可接受的 —— 一个健壮的程序应该能 “优雅地处理错误”,并给出友好提示。这篇就带你学习 “异常处理” 机制,再掌握几个实用的调试技巧,让你的代码从 “脆弱” 变得 “抗造”。

一、异常:代码运行时的 “意外状况”

先明确一个概念:异常(Exception) 是程序运行时发生的错误,比如语法正确但逻辑出错(比如除以 0、打开不存在的文件)。当异常发生时,Python 会终止程序并显示错误信息(Traceback),这就是我们常说的 “程序崩了”。

1. 常见的异常类型(新手必知)

先认识几种最容易遇到的异常,以后看到错误提示能快速定位问题:

异常类型含义例子
ValueError数据类型正确但值不合适int("abc")(字符串无法转整数)
TypeError数据类型错误"2" + 3(字符串和数字不能相加)
FileNotFoundError找不到文件open("不存在的文件.txt")
ZeroDivisionError除以 010 / 0
IndexError列表索引越界[1,2,3][5](列表只有 3 个元素)

示例:触发一个异常

# 让用户输入数字,转成整数
num = int(input("请输入一个数字:"))  # 如果用户输入"abc",会触发ValueError
print("你输入的数字是:", num)

如果输入 “abc”,程序会报错:

ValueError: invalid literal for int() with base 10: 'abc'

然后直接终止,体验非常差。

二、异常处理:用 try-except 捕获错误

Python 提供了try-except语句来 “捕获” 异常,让程序在出错时不崩溃,而是执行我们指定的处理逻辑(比如提示用户 “输入错误,请重新输入”)。

1. 最基础的 try-except 结构

try:
    # 可能发生异常的代码(放在try块里)
    代码块
except 异常类型:
    # 发生指定异常时,执行这里的代码
    处理逻辑

示例:处理 ValueError

try:
    num = int(input("请输入一个数字:"))
    print("你输入的数字是:", num)
except ValueError:
    # 当输入无法转成整数时,执行这里
    print("输入错误!请输入有效的数字。")

现在如果输入 “abc”,程序不会崩溃,而是打印 “输入错误!请输入有效的数字。”,更友好了。

2. 捕获多种异常(分情况处理)

如果一段代码可能触发多种异常,可以用多个except块分别处理:

try:
    a = int(input("请输入第一个数字:"))
    b = int(input("请输入第二个数字:"))
    result = a / b
    print(f"{a} ÷ {b} = {result}")
except ValueError:
    print("输入错误!请输入数字。")
except ZeroDivisionError:
    print("错误!除数不能为0。")

测试不同情况:

  • 输入 “10” 和 “abc”→ 触发ValueError→ 提示 “输入错误!”
  • 输入 “10” 和 “0”→ 触发ZeroDivisionError→ 提示 “除数不能为 0”
  • 输入 “10” 和 “2”→ 正常执行→ 输出 “10 ÷ 2 = 5.0”

3. 捕获所有异常(不推荐,但应急可用)

如果不确定会发生什么异常,可以用except Exception捕获所有异常(Exception是所有异常的父类):

try:
    # 可能出错的代码
    file = open("data.txt", "r")
    content = file.read()
    file.close()
except Exception as e:  # as e 可以获取错误信息
    print(f"发生错误:{e}")  # 打印错误详情

注意: 这种方式会 “隐藏” 所有错误,包括你没预料到的问题,可能导致调试困难。尽量只捕获你能处理的具体异常,而不是用 “一刀切” 的方式。

4. 可选的 else 和 finally 块

try-except还可以搭配else(无异常时执行)和finally(无论是否有异常都执行):

try:
    num = int(input("请输入数字:"))
except ValueError:
    print("输入错误")
else:
    # 没有异常时,才会执行这里
    print(f"你输入的数字是{num},平方是{num**2}")
finally:
    # 无论有没有异常,都会执行这里(常用于清理操作,比如关闭文件)
    print("--- 操作结束 ---")

运行效果:

  • 输入 “5”→ 无异常→ 执行 else 和 finally→ 输出:你输入的数字是5,平方是25--- 操作结束 ---
  • 输入 “abc”→ 有异常→ 执行 except 和 finally→ 输出:输入错误--- 操作结束 ---

三、主动抛出异常:用 raise 控制错误

除了处理 “意外发生的异常”,有时我们需要 “主动抛出异常” 来控制程序。比如写一个函数,要求参数必须是正数,如果传入负数,就主动报错。

示例:主动抛出异常

def calculate_square_root(n):
    if n < 0:
        # 主动抛出ValueError,附带错误信息
        raise ValueError("错误:不能计算负数的平方根")
    return n **0.5  # 计算平方根

try:
    print(calculate_square_root(25))  # 正常:5.0
    print(calculate_square_root(-5))  # 触发主动抛出的异常
except ValueError as e:
    print(e)  # 打印:错误:不能计算负数的平方根

主动抛异常的好处是:在函数内部提前检查错误,让错误信息更明确,方便调用者处理。

四、调试:快速找到代码中的 “bug”

即使学会了异常处理,代码还是可能出各种问题(比如逻辑错误:计算结果不对,但不报错)。这时候就需要 “调试”—— 一步步排查代码,找到错误原因。

1. 最基础的调试方法:print () 打印中间结果

新手最容易上手的方式:在关键步骤用print()输出变量值,看是否符合预期。

示例:调试一个计算平均分的函数

def calculate_average(scores):
    print("输入的成绩列表:", scores)  # 打印输入数据
    total = 0
    for score in scores:
        total += score
        print(f"累加后:{total}(当前分数:{score})")  # 打印循环中的中间值
    avg = total / len(scores)
    print("总分:", total, ",人数:", len(scores), ",平均分:", avg)  # 打印计算过程
    return avg

# 测试函数
average = calculate_average([85, 92, "78"])  # 这里有个bug:列表里混了字符串
print("最终平均分:", average)

运行后会打印:

输入的成绩列表: [85, 92, '78']
累加后:85(当前分数:85)
累加后:177(当前分数:92)
TypeError: unsupported operand type(s) for +=: 'int' and 'str'

通过打印的中间结果,能快速发现:循环到第三个元素时,score是字符串'78',导致无法累加,从而定位 bug。

2. 更专业的调试工具:PyCharm 断点调试

如果代码很长,print()会显得繁琐。PyCharm 的 “断点调试” 可以让程序 “一步步运行”,随时查看变量值,更高效。

步骤:1.** 打断点 :在代码行的左边点击,出现红色圆点(断点表示程序运行到这里会暂停);2. 启动调试 :右键点击代码文件,选择 “Debug 文件名”;3. 控制运行 - Step Over(F8):执行当前行,跳到下一行(不进入函数内部);- Step Into(F7):如果当前行是函数调用,进入函数内部;- Resume Program(F9):继续运行到下一个断点;4. 查看变量 **:在底部的 “Debug” 窗口中,能看到所有变量的当前值。

示例:用断点调试平均分函数for score in scores:这一行打断点,启动调试后,程序会暂停在这里。通过 Step Over 一步步执行,能看到score的值从 85→92→'78',当执行到total += score时,会触发TypeError,此时能清晰看到是'78'导致的错误。

3. 调试常见误区(新手避坑)

-** 不要凭感觉猜 bug :遇到问题先看错误提示(Traceback),定位到报错的行,再检查该行及附近的代码;- 简化问题 :如果代码复杂,先把无关部分删掉,用最小的示例复现问题(比如测试平均分函数时,用[1,2,3]这样的简单数据);- 善用搜索引擎 **:把错误提示(比如TypeError: unsupported operand type(s))复制到百度 / 谷歌,通常能找到解决方案。

五、实战:给学生管理系统添加异常处理

给之前的 “学生信息管理系统” 加上异常处理,解决用户输入错误(比如年龄输入文字、成绩输入负数)的问题。

改进后的核心代码:

def add_student(students):
    try:
        name = input("请输入姓名:").strip()  # 去除首尾空格
        if not name:  # 检查姓名是否为空
            raise ValueError("姓名不能为空!")
        
        age = int(input("请输入年龄:"))
        if age < 0 or age > 150:  # 检查年龄合理性
            raise ValueError("年龄必须在0-150之间!")
        
        score = float(input("请输入成绩:"))
        if score < 0 or score > 100:  # 检查成绩合理性
            raise ValueError("成绩必须在0-100之间!")
        
        students.append({"name": name, "age": age, "score": score})
        print(f"已添加学生:{name}")
    
    except ValueError as e:
        print(f"添加失败:{e}")  # 捕获所有输入相关的错误
    except Exception as e:
        print(f"发生未知错误:{e}")  # 捕获其他意外错误

# 主程序中调用add_student(students)

改进后效果:

  • 输入空姓名→ 主动抛异常→ 提示 “姓名不能为空!”
  • 年龄输入 “abc”→ 触发ValueError→ 提示 “添加失败:invalid literal for int () with base 10: 'abc'”
  • 成绩输入 “150”→ 主动抛异常→ 提示 “成绩必须在 0-100 之间!”
  • 所有错误都不会导致程序崩溃,用户可以重新操作。

下一篇预告:面向对象编程入门,代码更 “模块化”

学会了异常处理和调试,你的程序已经足够 “健壮” 了。但如果要开发大型项目(比如一个完整的管理系统),用之前的 “函数 + 数据” 的方式,代码会越来越难维护。下一篇会带你入门 “面向对象编程(OOP)”—— 这是一种更先进的编程思想,通过 “类和对象” 把数据和功能封装在一起,让代码更易扩展、更易复用,是 Python 高级开发的基础。

如果这篇的异常处理技巧帮你解决了代码崩溃的问题,欢迎在评论区分享你的经历:比如你遇到过什么棘手的异常,是怎么解决的,我们一起交流~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐