Python 基础语法 4:异常处理 try-except

前面我们学了变量、循环、函数,写出的代码能按逻辑运行了 —— 但现实中总会有 “意外”:比如让用户输入数字,他偏输文字;计算时不小心除以零;打开文件时文件被删了…… 这些情况如果不处理,程序会直接崩溃(弹出一堆红色错误),体验很差。

try-except异常处理机制,就是给程序加 “应急预案”:提前预判可能出问题的地方,一旦出错就执行备用方案,让程序 “优雅容错” 而不是崩溃。今天用生活案例讲透异常处理的逻辑和用法,新手也能轻松掌握。

一、先搞懂:什么是 “异常”?用生活案例类比

生活中处处有 “异常”(意外状况):

  1. 你计划 “用洗衣机洗衣服”,结果 “停电了”(意外);
  2. 你想 “打开冰箱拿牛奶”,结果 “牛奶喝完了”(意外);
  3. 你计算 “购物总价”,结果 “计算器没电了”(意外)。

在 Python 中,异常(Exception)就是程序运行时遇到的错误,比如:

  1. 输入错误:让用户输数字,他输了 “abc”(ValueError);
  2. 计算错误:用数字除以 0(ZeroDivisionError);
  3. 类型错误:用字符串和数字做加法(TypeError);
  4. 文件错误:打开一个不存在的文件(FileNotFoundError)。

这些异常如果不处理,程序会立刻停止运行,并显示 “Traceback”(错误追踪)信息,比如:

# 错误示例:除以零

a = 10 / 0  # 运行后直接崩溃

会弹出红色错误:

Traceback (most recent call last):

  File "test.py", line 1, in <module>

    a = 10 / 0

ZeroDivisionError: division by zero  # 明确告诉你:除以零错误

二、为什么需要异常处理?—— 让程序更 “抗造”

没有异常处理的程序,就像 “玻璃心”:一点小错就崩溃;有了异常处理,程序就有了 “抗压能力”,能在出错时:

  1. 不崩溃:继续执行后续代码;
  2. 给提示:告诉用户 “哪里错了,该怎么办”(而不是一堆晦涩的错误代码);
  3. 留记录:把错误信息保存到日志,方便后续排查问题。

比如一个 “让用户输入数字并计算平方” 的程序,没有异常处理时,用户输文字会崩溃;有了异常处理,会提示 “请输入正确的数字”,并让用户重新输入。

三、异常处理基础:try-except 基本结构(核心)

try-except的逻辑就像 “应急预案”:

预案步骤:

1. 尝试做一件可能出错的事(try块);

2. 如果出错了,就执行备用方案(except块);

3. 如果没出错,就跳过备用方案,继续往下走。

1. 语法格式:

try:

    可能发生异常的代码块  # 尝试执行这里的代码

except 异常类型:  # 预判可能出现的异常类型(比如ZeroDivisionError

    异常发生时执行的代码块  # 出错了就执行这里(备用方案)

2. 案例 1:处理 “除以零” 异常(ZeroDivisionError)

比如写一个 “计算两数相除” 的程序,提前处理 “除数为 0” 的情况:

def divide(a, b):

    try:

        # 尝试执行除法(可能出错的步骤)

        result = a / b

        print(f"计算结果:{result}")

    except ZeroDivisionError:

        # 如果除数为0,执行这里的备用方案

        print("出错了!除数不能为0,请重新输入。")

# 测试正常情况(不出错)

divide(10, 2)  # 输出:计算结果:5.0try块执行,except块跳过)

# 测试异常情况(除数为0

divide(10, 0)  # 输出:出错了!除数不能为0,请重新输入。(except块执行,程序不崩溃)

3. 案例 2:处理 “输入类型错误”(ValueError)

比如让用户输入数字,用户可能输文字,用try-except处理:

try:

    # 尝试把用户输入的内容转成整数(可能出错:输入不是数字)

    num = int(input("请输入一个数字:"))

    print(f"你输入的数字是:{num}")

except ValueError:

    # 输入不是数字时,执行备用方案

    print("输入错误!请输入一个有效的数字(比如123)。")

# 测试正常输入(输数字)

# 请输入一个数字:100 → 输出:你输入的数字是:100try块执行)

# 测试异常输入(输文字)

# 请输入一个数字:abc → 输出:输入错误!请输入一个有效的数字(比如123)。(except块执行)

四、捕获多个异常:针对性处理不同错误

一个程序可能出现多种异常(比如既可能除数为 0,又可能输入不是数字),可以用多个except分别处理,就像 “针对不同意外准备不同预案”。

1. 语法格式:

try:

    可能发生异常的代码块

except 异常类型1:  # 处理第一种异常

    处理代码1

except 异常类型2:  # 处理第二种异常

    处理代码2

except (异常类型3, 异常类型4):  # 用元组把多个异常放一起,用同一套处理逻辑

    处理代码3

2. 案例:一个程序处理 3 种异常

写一个 “让用户输入两个数字,计算除法” 的程序,可能出现 3 种错误:

  1. 输入不是数字(ValueError);
  2. 除数为 0(ZeroDivisionError);
  3. 其他未知错误(用Exception捕获,它是所有异常的父类)。

try:

    # 步骤1:输入第一个数字(可能出错:非数字)

    num1 = int(input("请输入被除数:"))

    # 步骤2:输入第二个数字(可能出错:非数字)

    num2 = int(input("请输入除数:"))

    # 步骤3:计算除法(可能出错:除数为0

    result = num1 / num2

    print(f"{num1} ÷ {num2} = {result}")

except ValueError:

    # 处理输入不是数字的错误

    print("输入错误!请确保输入的是整数(比如1020)。")

except ZeroDivisionError:

    # 处理除数为0”的错误

    print("计算错误!除数不能为0")

except Exception as e:  # 捕获其他所有未预料到的错误,用as e获取错误详情

    # 处理未知错误(调试时很有用)

    print(f"发生了未知错误:{e}")  # e是错误的具体信息

# 测试1:输入非数字(比如输入"abc"

# 输出:输入错误!请确保输入的是整数(比如1020)。

# 测试2:除数为0(比如被除数10,除数0

# 输出:计算错误!除数不能为0

# 测试3:其他错误(比如num1 = 10num2 = "2" → 但这里num2已转成int,所以不会,仅举例)

# 输出:发生了未知错误:...(具体错误信息)

关键:异常类型的 “父子关系”

Exception是所有内置异常的 “父类”,如果把except Exception放在最前面,会 “拦截” 所有异常,后面的except块永远不会执行(因为父类异常会先捕获)。

正确顺序:先写具体的异常(子类),最后写Exception(父类)兜底,比如:

# 正确顺序:具体异常在前,父类异常在后

try:

    # ...

except ValueError:

    # 处理ValueError

except ZeroDivisionError:

    # 处理ZeroDivisionError

except Exception:

    # 处理其他异常(兜底)

五、else 子句:没出错时执行的代码

如果想在 “没发生异常” 时执行一些代码(比如 “操作成功” 的提示),可以用else子句,它和try-except配合使用,逻辑更清晰。

语法格式:

try:

    可能出错的代码

except 异常类型:

    出错时执行的代码

else:

    没出错时执行的代码  # 只有try块没异常,才会执行这里

案例:计算成功时提示 “操作完成”

try:

    num1 = int(input("请输入被除数:"))

    num2 = int(input("请输入除数:"))

    result = num1 / num2

except ValueError:

    print("输入错误:请输入数字。")

except ZeroDivisionError:

    print("计算错误:除数不能为0")

else:

    # 只有上面没出错,才会执行这里

    print(f"计算成功!{num1} ÷ {num2} = {result}")

    print("操作完成~")

# 测试正常输入(102):

# 输出:计算成功!10 ÷ 2 = 5.0 → 操作完成~

# 测试异常输入(输"abc"):

# 输出:输入错误:请输入数字。(else块不执行)

六、finally 子句:无论是否出错都必须执行的代码

有些操作 “无论是否出错都必须执行”(比如打开文件后必须关闭,连接数据库后必须断开),用finally子句,它永远会执行,即使tryexcept里有return

语法格式:

try:

    可能出错的代码

except 异常类型:

    出错时执行的代码

finally:

    无论是否出错,都会执行的代码  # 必执行,比如清理资源

案例:模拟 “打开文件后必须关闭”

def read_file():

    file = None  # 先定义变量,避免except块里引用未定义的变量

    try:

        # 尝试打开文件(可能出错:文件不存在)

        file = open("test.txt", "r")  # 假设test.txt不存在

        content = file.read()

        print(f"文件内容:{content}")

    except FileNotFoundError:

        print("出错了:文件不存在。")

    finally:

        # 无论是否出错,都要关闭文件(如果文件已打开)

        if file is not None:  # 避免file未赋值时调用close()

            file.close()

            print("文件已关闭(即使出错也执行)")

read_file()

# 运行结果(test.txt不存在时):

# 出错了:文件不存在。

# 文件已关闭(即使出错也执行)

为什么需要finally

如果没有finally,关闭文件的代码要在tryexcept里各写一次(冗余);有了finally,只写一次即可,保证 “清理操作” 必执行。

七、主动抛出异常:raise 关键字

除了处理 “程序自动产生的异常”,我们还能 “主动抛出异常”(比如验证用户输入时,发现年龄为负数,主动提示错误),用raise关键字。

语法格式:

if 不符合条件的情况:

    raise 异常类型("错误提示信息")  # 主动抛出异常

案例:验证年龄必须为正数

def check_age(age):

    if age < 0:

        # 主动抛出ValueError,并附带提示信息

        raise ValueError("年龄不能为负数!请输入大于等于0的数字。")

    print(f"年龄验证通过:{age}")

# 测试正常情况(年龄18

check_age(18)  # 输出:年龄验证通过:18

# 测试异常情况(年龄-5

try:

    check_age(-5)  # 主动抛出的异常,需要用try-except捕获

except ValueError as e:

    print(f"捕获到错误:{e}")  # 输出:捕获到错误:年龄不能为负数!请输入大于等于0的数字。

作用:提前暴露错误,避免后续逻辑出错

比如计算 “出生年份 = 当前年份 - 年龄”,如果年龄是负数,不主动抛出异常,后续会算出 “2023 - (-5) = 2028” 这种荒谬结果;主动抛出异常,能在源头阻止错误。

八、新手易错点总结

  1. except 块顺序错误

except Exception(父类异常)放在前面,会导致后面的具体异常(如ValueError)永远不执行,正确顺序是 “具体异常在前,父类异常在后”。

  1. 空 except 块(吞噬错误)

except:不指定异常类型,会捕获所有异常(包括KeyboardInterrupt这种用户中断程序的操作),而且不做任何处理,导致错误被隐藏,难以调试。

❌ 错误示例:

try:

    10 / 0

except:  # except,吞噬错误

    pass  # 什么都不做

  1. finally 里用 return

finally里的return会覆盖tryexcept里的return,导致返回值不符合预期,尽量别在finally里用return

  1. 未捕获的异常

只处理了部分异常,遗漏了其他可能的错误(比如只处理ValueError,没处理TypeError),导致程序仍可能崩溃,建议最后用except Exception兜底(但要记录错误信息)。

  1. 主动抛异常后没捕获

raise抛出异常后,如果不在调用处用try-except捕获,程序会直接崩溃,记得 “抛异常的地方” 和 “用异常的地方” 要配合。

九、练手小任务(动手巩固!)

  1. 任务 1:写一个 “安全除法” 函数

函数接收两个参数ab,返回a/b的结果。要求:

    1. 如果b为 0,返回None并提示 “除数不能为 0”;
    2. 如果ab不是数字(比如字符串),返回None并提示 “参数必须是数字”;
    3. 其他错误返回None并提示 “发生未知错误”。
  1. 任务 2:处理用户输入的年龄,直到输入合法

循环让用户输入年龄,要求:

    1. 年龄必须是整数,且在 0-120 之间;
    2. 如果输入不是整数,提示 “请输入数字”;
    3. 如果年龄 <0 或> 120,主动抛出ValueError并提示 “年龄必须在 0-120 之间”;
    4. 输入合法则退出循环,打印 “年龄输入正确:xx 岁”。
  1. 任务 3:模拟文件读写的异常处理

写一个函数read_and_write(input_file, output_file),功能:

    1. 读取input_file的内容(假设文件内容是字符串);
    2. 把内容写入output_file
    3. 处理 “输入文件不存在”(FileNotFoundError)、“没有写入权限”(PermissionError)等异常;
    4. 无论是否出错,都打印 “操作结束”。

下一篇我们会讲 “Python 基础语法 5:模块与包”,学习如何用别人写好的代码(比如math库、random库),以及如何组织自己的代码,让程序结构更清晰。如果练手时遇到问题,评论区留言,我会帮你分析解决!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小杰杂谈弱电知识

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值