【Python入门】8.异常

Python 异常处理完全指南(系统梳理+实战示例)

异常是 Python 程序运行时发生的意外错误(如除零、索引越界、文件不存在等),会导致程序默认终止并抛出错误信息。掌握异常处理,能让程序更健壮(避免崩溃)、错误排查更高效。本文从基础概念→核心语法→进阶用法→最佳实践,系统梳理 Python 异常知识。

一、异常的基本概念

1.1 异常 vs 语法错误

很多初学者会混淆「异常」和「语法错误」,两者本质不同:

类型发生时机能否捕获示例
语法错误程序执行前(解释器编译)不能缩进错误、括号不匹配、语法错误
运行时异常程序执行中除零、索引越界、文件不存在

示例:语法错误(无法捕获)

print("Hello"  # 缺少右括号,解释器直接报错,程序无法启动

报错信息:SyntaxError: unexpected EOF while parsing

示例:运行时异常(可捕获)

1 / 0  # 执行时触发除零异常,程序默认终止

报错信息:ZeroDivisionError: division by zero

1.2 异常的默认行为

当程序触发未处理的异常时,Python 会执行 3 步:

  1. 中断当前程序执行流程;
  2. 打印异常信息(包含异常类型、错误描述、出错位置的调用栈);
  3. 终止程序。

调用栈信息是调试关键,例如:

def divide(a, b):
    return a / b

divide(1, 0)

报错信息包含调用栈(明确出错行):

ZeroDivisionError: division by zero
  File "test.py", line 4, in <module>
    divide(1, 0)
  File "test.py", line 2, in divide
    return a / b

二、异常处理的核心语法

Python 用 try-except-else-finally 语句捕获和处理异常,各子句分工明确,可灵活组合。

2.1 基础结构:try + except(核心)

作用:捕获 try 块中可能发生的异常,避免程序崩溃。
语法

try:
    # 可能触发异常的代码(核心执行逻辑)
    危险操作
except 异常类型1:
    # 当 try 块触发「异常类型1」时,执行此处代码
    异常处理逻辑1
except 异常类型2:
    # 当 try 块触发「异常类型2」时,执行此处代码
    异常处理逻辑2

示例:捕获除零异常

try:
    result = 1 / 0
except ZeroDivisionError:
    print("错误:除数不能为 0!")  # 捕获到异常,执行处理逻辑
print("程序继续执行...")  # 不会终止,正常输出

输出:

错误:除数不能为 0!
程序继续执行...

2.2 捕获多个异常(2种方式)

方式1:多个 except 子句(推荐,逻辑清晰)

针对不同异常类型,编写不同处理逻辑:

try:
    lst = [1, 2, 3]
    print(lst[10])  # 触发 IndexError
    # result = 1 / 0  # 触发 ZeroDivisionError
except IndexError:
    print("错误:索引超出列表范围!")
except ZeroDivisionError:
    print("错误:除数不能为 0!")
方式2:元组形式捕获多个异常(同一种处理逻辑)

如果多个异常的处理方式相同,可合并为一个 except

try:
    # 可能触发 IndexError 或 KeyError 的代码
    d = {"name": "Python"}
    print(d["age"])  # KeyError
except (IndexError, KeyError) as e:  # as e:捕获异常对象,可获取详细信息
    print(f"错误类型:{type(e).__name__}")
    print(f"错误描述:{e}")  # 打印具体错误信息

输出:

错误类型:KeyError
错误描述:'age'

2.3 捕获所有异常(谨慎使用)

不推荐写法:except:(捕获所有异常,包括系统退出异常)
try:
    1 / 0
except:  # 捕获所有异常(包括 KeyboardInterrupt、SystemExit 等系统异常)
    print("发生了未知错误!")

问题:可能掩盖严重错误(如用户按 Ctrl+C 退出的 KeyboardInterrupt),导致调试困难。

推荐写法:except Exception:(捕获所有用户级异常)

Exception 是所有非系统退出异常的基类,不会捕获 SystemExitKeyboardInterrupt 等系统级异常:

try:
    1 / 0
except Exception as e:  # 安全捕获所有用户异常
    print(f"异常信息:{e}")

2.4 else 子句(可选)

作用:仅当 try没有触发任何异常时,才执行 else 中的代码(逻辑上是「无异常时的后续操作」)。
语法位置:在所有 except 之后,finally 之前。

try:
    result = 10 / 2  # 无异常
except ZeroDivisionError:
    print("除数不能为 0!")
else:
    print(f"计算成功,结果:{result}")  # 会执行

输出:计算成功,结果:5.0

2.5 finally 子句(可选)

作用:无论 try 块是否触发异常,finally 中的代码一定会执行(常用于「资源清理」,如关闭文件、数据库连接)。
语法位置:在所有 except/else 之后。

示例:文件操作的资源清理

file = None
try:
    file = open("test.txt", "r")  # 尝试打开文件
    content = file.read()
except FileNotFoundError:
    print("错误:文件不存在!")
else:
    print("文件内容:", content)
finally:
    # 无论是否异常,都关闭文件(避免资源泄露)
    if file:
        file.close()
        print("文件已关闭")

注意:finally 中的 return 会覆盖 try/except/else 中的 return(不推荐在 finally 中写 return)。

2.6 上下文管理器(with 语句):简化资源清理

with 语句是 try-finally 的语法糖,自动帮你完成资源清理(如关闭文件、网络连接),无需手动写 finally

# 等价于上面的文件操作示例,自动关闭文件
try:
    with open("test.txt", "r") as file:  # 进入 with 块:打开文件;退出 with 块:自动关闭
        content = file.read()
except FileNotFoundError:
    print("文件不存在!")
else:
    print("文件内容:", content)

with 语句适用于所有实现了「上下文管理协议」的对象(如文件、数据库连接、锁等)。

三、Python 常见内置异常

Python 提供了大量内置异常(均继承自 BaseException),以下是开发中最常用的类型,按场景分类:

3.1 数据操作相关

异常类型触发场景示例
TypeError类型不匹配(如字符串+数字)"2" + 3
ValueError值合法但不符合要求(如字符串转数字失败)int("abc")
ZeroDivisionError除零1 / 0
OverflowError数值溢出(Python 中少见,如大整数运算)pow(10, 1000000)(一般不触发)

3.2 容器(列表/字典/元组)相关

异常类型触发场景示例
IndexError列表/元组索引越界[1,2,3][10]
KeyError字典访问不存在的键{"a":1}["b"]
AttributeError访问对象不存在的属性/方法s = "hello"; s.foo()
IndexError字符串索引越界"abc"[5]

3.3 文件/IO 相关

异常类型触发场景示例
FileNotFoundError打开不存在的文件open("不存在.txt")
PermissionError无文件操作权限(如只读文件写入)open("read_only.txt", "w")
IOError通用 IO 错误(Python3 中部分合并到其他异常)磁盘满时写入文件

3.4 其他常见异常

异常类型触发场景示例
NameError访问未定义的变量/函数print(undefined_var)
ImportError导入模块失败(模块不存在或路径错误)import non_existent_module
KeyboardInterrupt用户按 Ctrl+C 终止程序运行时按 Ctrl+C
MemoryError内存不足(如创建超大列表)[1]*10**18

四、主动抛出异常:raise 语句

除了系统自动触发异常,你也可以用 raise 主动抛出异常(常用于「参数校验」「业务规则违规」等场景)。

4.1 基本用法

raise 异常类型  # 抛出指定类型的异常
raise 异常类型("错误描述")  # 带自定义错误信息(推荐)

示例:参数校验

def register(name, age):
    if not name:
        # 主动抛出 ValueError,描述错误原因
        raise ValueError("用户名不能为空!")
    if age < 18:
        raise ValueError(f"年龄需≥18,当前年龄:{age}")
    print(f"注册成功:{name}{age}岁)")

try:
    register("张三", 16)  # 触发主动抛出的 ValueError
except ValueError as e:
    print(f"注册失败:{e}")  # 输出:注册失败:年龄需≥18,当前年龄:16

4.2 重新抛出异常(保留原始调用栈)

有时需要先捕获异常做一些处理,再重新抛出(不掩盖原始错误):

try:
    1 / 0
except ZeroDivisionError as e:
    print("记录错误日志:", e)  # 先记录日志
    raise  # 重新抛出原始异常(保留调用栈)
    # raise e  # 等价于 raise,但 Python3 中推荐直接用 raise

4.3 异常链(raise ... from ...

当一个异常导致另一个异常时,用 raise 新异常 from 原始异常 保留异常链(方便调试):

try:
    open("data.txt")  # 触发 FileNotFoundError
except FileNotFoundError as original_e:
    # 抛出新异常,并关联原始异常
    raise RuntimeError("数据文件缺失,无法继续执行") from original_e

报错信息会显示两个异常的关联关系:

RuntimeError: 数据文件缺失,无法继续执行
The above exception was the direct cause of the following exception:
FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'

五、自定义异常

当 Python 内置异常无法满足业务需求时(如「用户不存在」「余额不足」等业务错误),可以自定义异常类。

5.1 自定义异常的规则

  1. 必须继承自 Exception(或其子类),不要继承 BaseExceptionBaseException 包含系统级异常,如 SystemExit);
  2. 通常只需要定义类名(语义化),无需额外方法(如需扩展可添加自定义属性)。

5.2 示例:业务自定义异常

# 自定义异常(继承 Exception)
class UserNotFoundError(Exception):
    """用户不存在异常(业务异常)"""
    pass

class InsufficientBalanceError(Exception):
    """余额不足异常(业务异常)"""
    def __init__(self, balance, need):
        # 自定义异常属性,方便处理
        self.balance = balance
        self.need = need
        # 调用父类构造器,设置错误描述
        super().__init__(f"余额不足:当前 {balance},需 {need}")

# 使用自定义异常
def withdraw(user, amount):
    users = {"张三": 1000, "李四": 500}
    if user not in users:
        raise UserNotFoundError(f"用户「{user}」不存在")
    balance = users[user]
    if balance < amount:
        raise InsufficientBalanceError(balance, amount)
    print(f"提现成功:{user} 提取 {amount} 元,剩余 {balance - amount} 元")

try:
    withdraw("王五", 800)
except UserNotFoundError as e:
    print(f"业务错误:{e}")
except InsufficientBalanceError as e:
    print(f"业务错误:{e},当前余额:{e.balance}")

六、异常处理的最佳实践

掌握语法后,更重要的是规范使用异常处理,避免常见坑:

6.1 捕获具体异常,而非泛型异常

坏示例:用 except:except Exception: 捕获所有异常,掩盖真实错误:

try:
    lst = [1,2,3]
    print(lst[10])
except:  # 不推荐,无法定位错误类型
    print("出错了!")

好示例:捕获具体异常,针对性处理:

try:
    lst = [1,2,3]
    print(lst[10])
except IndexError:
    print("错误:索引越界,请检查列表长度!")

6.2 不要过度捕获异常

仅捕获「可预期、可处理」的异常,不要为了「避免崩溃」而捕获所有异常。例如,KeyboardInterrupt(用户终止程序)不应捕获:

try:
    while True:
        print("运行中...")
except Exception:  # 不会捕获 KeyboardInterrupt,用户可正常终止
    print("处理已知异常...")

6.3 提供清晰的错误信息

异常信息应明确说明「什么错、为什么错、怎么改」,方便调试和用户理解:

# 坏示例
raise ValueError("参数错误")

# 好示例
raise ValueError("参数 age 必须是 18-60 之间的整数,当前值:'abc'")

6.4 用 finallywith 清理资源

文件、网络连接、数据库连接等资源,必须确保关闭,优先用 with 语句,其次用 finally

# 推荐:with 自动关闭数据库连接(假设使用 sqlite3)
import sqlite3
with sqlite3.connect("test.db") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")

# 备选:finally 手动关闭
conn = None
try:
    conn = sqlite3.connect("test.db")
    # 数据库操作
except Exception as e:
    print("数据库错误:", e)
finally:
    if conn:
        conn.close()

6.5 避免沉默异常(Swallowed Exceptions)

捕获异常后,要么处理(修复错误、提示用户),要么重新抛出,不要捕获后不做任何操作:

# 坏示例:沉默异常,无法排查问题
try:
    1 / 0
except ZeroDivisionError:
    pass  # 不做任何处理,错误被掩盖

# 好示例:要么处理,要么抛出
try:
    1 / 0
except ZeroDivisionError as e:
    print(f"错误:{e}")  # 处理(提示用户)
    # 或重新抛出(如果当前无法处理)
    # raise

6.6 自定义异常用语义化命名

自定义异常类名应包含「错误场景」,以 Error 结尾(如 UserNotFoundErrorPaymentFailedError),便于阅读和区分。

七、总结

Python 异常处理的核心是「预判错误→捕获错误→处理错误→清理资源」,关键知识点:

  1. 异常是运行时错误,区别于语法错误;
  2. 核心语法:try-except-else-finallywith 语句简化资源清理;
  3. 捕获异常:优先具体异常,慎用泛型捕获;
  4. 主动抛出:raise 用于参数校验、业务违规;
  5. 自定义异常:继承 Exception,语义化命名;
  6. 最佳实践:不沉默异常、不过度捕获、清晰错误信息。

通过合理的异常处理,你的 Python 程序将更健壮、更易维护!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值