当前位置:首页>python>Python零基础入门(五):异常处理

Python零基础入门(五):异常处理

  • 2026-10-03 16:52:32
Python零基础入门(五):异常处理

Python零基础入门(五):异常处理

前言

前四篇我们完成了环境搭建与数据类型、运算符与条件判断、面向对象编程基础和模块与包导入。你已经能让程序"算"、能"想"、能"组织"——但程序运行中总会遇到各种意外:文件不存在、网络连接失败、除以零、用户输入非法数据……

这些"意外"就是异常。如果不对异常进行处理,程序会直接崩溃并抛出错误信息。一个健壮的程序,必须能够优雅地应对各种异常情况,而不是一遇到错误就"罢工"。

本篇将系统讲解 Python 的异常处理机制,让你的程序更加稳定可靠。


一、什么是异常?

异常(Exception)  是程序运行过程中发生的错误事件。当 Python 遇到无法正常执行的情况时,会"抛出"一个异常对象。如果这个异常没有被捕获处理,程序就会终止并显示错误信息(称为"堆栈跟踪")。

1.1 异常示例

# 没有异常处理的代码def divide(a, b):    return a / bresult = divide(10, 0)  # ZeroDivisionError: division by zeroprint("这行永远不会执行")

程序在第 3 行就崩溃了,第 4 行根本不会执行。运行结果:

Traceback (most recent call last):  File "main.py", line 3, in <module>    result = divide(10, 0)ZeroDivisionError: division by zero

1.2 常见内置异常

Python 内置了丰富的异常类型,每种类型对应一种特定的错误情况:

异常类型
说明
触发场景
ValueError
值错误
int("abc")
TypeError
类型错误
"hello" + 5
KeyError
键不存在
dict["不存在的键"]
IndexError
索引越界
list[100]
FileNotFoundError
文件不存在
open("不存在.txt")
ZeroDivisionError
除以零
1/0
AttributeError
属性不存在
"hello".foo()
ImportError
导入失败
import 不存在的模块
NameError
变量未定义
print(未定义的变量)
RuntimeError
运行时错误
多种场景

二、try-except 基本语法

Python 使用 try-except 语句来捕获和处理异常:

try:    # 可能发生异常的代码    result = 10 / 0except ZeroDivisionError:    # 处理特定异常    print("错误:除数不能为零!")

2.1 捕获特定异常

try:    num = int(input("请输入数字:"))    result = 100 / numexcept ValueError:    print("输入的不是有效数字!")except ZeroDivisionError:    print("不能除以零!")

可以有多个 except 块,分别处理不同类型的异常。Python 会从上到下依次检查,匹配到第一个就执行,然后跳过剩余的 except 块。

2.2 捕获多个异常

如果多个异常的处理方式相同,可以合并写:

try:    # 可能发生多种异常的代码    value = int(input("输入数字:"))    result = 100 / valueexcept (ValueError, ZeroDivisionError) as e:    print(f"发生错误:{e}")

2.3 捕获异常对象

使用 as 关键字可以获取异常对象,获取详细的错误信息:

try:    with open("不存在的文件.txt", "r") as f:        content = f.read()except FileNotFoundError as e:    print(f"文件不存在:{e}")    # 输出:文件不存在:[Errno 2] No such file or directory: &#x27;不存在的文件.txt&#x27;

三、else 和 finally

除了 try 和 except,Python 还提供了 else 和 finally 子句:

try:    num = int(input("请输入数字:"))    result = 100 / numexcept ValueError:    print("输入无效")except ZeroDivisionError:    print("不能除以零")else:    # 没有发生异常时执行    print(f"结果是:{result}")finally:    # 无论是否发生异常都会执行(通常用于清理工作)    print("程序结束")

3.1 执行流程

  • • try 块:执行可能出错的代码
  • • except 块:发生异常时执行(可以有多个)
  • • else 块:没有异常时执行(可选)
  • • finally 块:无论如何都会执行(可选,常用于资源清理)

3.2 else 的使用场景

else 子句适用于"只有在没有异常时才执行"的代码,这样可以把可能抛出异常的代码限制在最小范围:

try:    f = open("data.txt", "r")except FileNotFoundError:    print("文件不存在")else:    # 只有文件成功打开后才读取    content = f.read()    f.close()    print(content)

3.3 finally 的使用场景

finally 子句无论如何都会执行,即使在 try 或 except 中有 return 语句。它常用于资源清理:

def read_file(filename):    f = None    try:        f = open(filename, "r")        return f.read()    except FileNotFoundError:        return "文件不存在"    finally:        # 无论成功还是失败,都确保文件被关闭        if f:            f.close()            print("文件已关闭")

四、万能异常捕获(谨慎使用)

使用 Exception 可以捕获几乎所有非系统退出异常:

try:    # 任意可能出错的代码    risky_operation()except Exception as e:    # 捕获所有非系统退出异常    print(f"发生了错误:{type(e).__name__}: {e}")

4.1 为什么不推荐滥用?

# 最糟糕的做法:静默吞掉异常try:    dangerous_operation()except:    pass  # 异常被忽略了,出了问题你根本不知道!

滥用 except Exception 的问题:

  1. 1. 隐藏真正的错误:你不知道程序哪里出了问题
  2. 2. 难以调试:程序"看起来正常",但结果可能是错的
  3. 3. 掩盖 bug:可能让程序在错误状态下继续运行

4.2 何时使用?

只在以下场景使用 except Exception:

  • • 最外层错误处理:捕获所有未预料到的异常,记录日志后安全退出
  • • 日志记录:记录异常信息后重新抛出
  • • 某些必须完成的操作:如资源清理
import loggingdef safe_operation():    try:        # 复杂的业务逻辑        result = complex_calculation()        return result    except Exception as e:        # 记录日志        logging.error(f"操作失败:{e}", exc_info=True)        # 重新抛出,让调用者决定如何处理        raise

五、raise 主动抛出异常

除了被动捕获异常,你还可以使用 raise 关键字主动抛出异常:

5.1 基本用法

def set_age(age):    if age < 0 or age > 150:        raise ValueError(f"年龄必须在 0-150 之间,当前值:{age}")    return agetry:    set_age(200)except ValueError as e:    print(f"错误:{e}")    # 输出:错误:年龄必须在 0-150 之间,当前值:200

5.2 重新抛出异常

在捕获异常后,可以用 raise 重新抛出(不带参数):

try:    result = 10 / 0except ZeroDivisionError:    print("记录日志:除零错误")    raise  # 重新抛出,让上层处理

5.3 异常链(Exception Chaining)

在捕获一个异常后抛出另一个异常,可以用 raise ... from ... 保留原始异常信息:

def convert_to_int(value):    try:        return int(value)    except ValueError as e:        raise TypeError(f"无法转换为整数:{value}") from etry:    convert_to_int("abc")except TypeError as e:    print(f"错误:{e}")    print(f"原始异常:{e.__cause__}")

六、自定义异常

当内置异常不能准确描述错误时,可以创建自己的异常类:

6.1 基本定义

class InsufficientFundsError(Exception):    """余额不足异常"""    pass# 使用balance = 100amount = 150if amount > balance:    raise InsufficientFundsError(f"余额不足:需要 {amount},当前余额 {balance}")

6.2 带属性的自定义异常

class InsufficientFundsError(Exception):    """余额不足异常"""    def __init__(self, balance, amount):        self.balance = balance        self.amount = amount        self.deficit = amount - balance        super().__init__(f"余额不足:需要 {amount},当前余额 {balance},缺少 {self.deficit}")class BankAccount:    def __init__(self, balance=0):        self.balance = balance    def withdraw(self, amount):        if amount > self.balance:            raise InsufficientFundsError(self.balance, amount)        self.balance -= amount        return self.balance# 使用account = BankAccount(100)try:    account.withdraw(150)except InsufficientFundsError as e:    print(f"取款失败:{e}")    print(f"缺少金额:{e.deficit}")

6.3 自定义异常的最佳实践

  1. 1. 继承Exception类(而不是 BaseException)
  2. 2. 命名以Error结尾(如 ValidationError、TimeoutError)
  3. 3. 提供有意义的错误信息
  4. 4. 可以添加额外的属性保存上下文信息
  5. 5. 组织异常层次:定义一个基类,然后派生具体异常
# 异常层次示例class AppError(Exception):    """应用异常基类"""    passclass ValidationError(AppError):    """数据验证错误"""    passclass DatabaseError(AppError):    """数据库错误"""    passclass ConnectionError(DatabaseError):    """数据库连接错误"""    pass

七、异常处理最佳实践

7.1 EAFP 原则

Python 社区推崇 EAFP(Easier to Ask Forgiveness than Permission,请求原谅比请求许可更容易)风格:

# LBYL(Look Before You Leap)风格 —— 先检查再操作if key in dictionary:    value = dictionary[key]else:    value = default_value# EAFP 风格 —— 先操作,出错再处理(更 Pythonic)try:    value = dictionary[key]except KeyError:    value = default_value

EAFP 的优势:

  1. 1. 代码更简洁:减少条件判断
  2. 2. 避免竞态条件:在多线程环境中更安全
  3. 3. 符合 Python 风格:Python 内置的很多操作都遵循这个原则

7.2 具体异常优于宽泛异常

# 不好:捕获所有异常try:    result = int(user_input)except Exception:    print("出错了")# 好:捕获特定异常try:    result = int(user_input)except ValueError:    print("请输入有效的数字")

7.3 不要忽略异常

# 最糟糕的做法try:    dangerous_operation()except:    pass# 至少记录一下import loggingtry:    dangerous_operation()except Exception as e:    logging.error(f"操作失败:{e}")

7.4 合理使用 finally

# finally 用于资源清理file = Nonetry:    file = open("data.txt", "r")    content = file.read()    process(content)except FileNotFoundError:    print("文件不存在")finally:    if file:        file.close()# 更好的方式:使用 with 语句(下一篇会详细讲)with open("data.txt", "r") as file:    content = file.read()

7.5 保持异常粒度适中

# 不好:一个 try 包含太多代码try:    data = read_file(filename)    parsed = parse_data(data)    result = process(parsed)    save_result(result)except Exception:    print("出错了")  # 哪一步出错?不知道!# 好:每个可能出错的操作单独处理try:    data = read_file(filename)except FileNotFoundError:    print("文件不存在")    returntry:    parsed = parse_data(data)except ParseError:    print("数据格式错误")    return# ... 以此类推

八、综合实战:健壮的用户输入处理

让我们结合所学知识,实现一个健壮的用户输入处理函数:

class InputError(Exception):    """输入错误基类"""    passclass OutOfRangeError(InputError):    """数值超出范围"""    def __init__(self, value, min_val, max_val):        self.value = value        self.min_val = min_val        self.max_val = max_val        super().__init__(f"数值 {value} 超出范围 [{min_val}, {max_val}]")class InvalidFormatError(InputError):    """格式无效"""    passdef get_integer(prompt, min_val=None, max_val=None):    """    获取用户输入的整数,带验证    Args:        prompt: 提示信息        min_val: 最小值(可选)        max_val: 最大值(可选)    Returns:        int: 验证通过的整数    Raises:        InvalidFormatError: 输入不是有效整数        OutOfRangeError: 数值超出范围    """    while True:        try:            user_input = input(prompt).strip()            if not user_input:                raise InvalidFormatError("输入不能为空")            value = int(user_input)            if min_val is not None and value < min_val:                raise OutOfRangeError(value, min_val, max_val or float(&#x27;inf&#x27;))            if max_val is not None and value > max_val:                raise OutOfRangeError(value, min_val or float(&#x27;-inf&#x27;), max_val)            return value        except ValueError:            print("错误:请输入有效的整数")        except InvalidFormatError as e:            print(f"错误:{e}")        except OutOfRangeError as e:            print(f"错误:{e}")        except KeyboardInterrupt:            print("\n用户取消输入")            raise SystemExitdef get_choice(prompt, options):    """    获取用户选择    Args:        prompt: 提示信息        options: 有效选项列表    Returns:        str: 用户选择的选项    """    while True:        try:            choice = input(prompt).strip().lower()            if choice not in options:                raise InvalidFormatError(f"无效选择,请输入:{&#x27;, &#x27;.join(options)}")            return choice        except InvalidFormatError as e:            print(f"错误:{e}")# 使用示例if __name__ == "__main__":    print("=== 用户信息录入 ===")    # 获取年龄(1-150)    age = get_integer("请输入年龄:", min_val=1, max_val=150)    print(f"年龄:{age}")    # 获取分数(0-100)    score = get_integer("请输入分数:", min_val=0, max_val=100)    print(f"分数:{score}")    # 获取选择    action = get_choice("请选择操作 (y/n): ", ["y", "n", "yes", "no"])    print(f"选择:{action}")    print("录入完成!")

这个例子展示了:

  1. 1. 自定义异常类:继承体系,携带额外信息
  2. 2. 异常链:保留原始错误信息
  3. 3. 循环重试:捕获异常后让用户重新输入
  4. 4. 多种异常处理:分别处理不同类型的错误
  5. 5. 键盘中断处理:优雅地处理 Ctrl+C

九、动手练习

  1. 1. 写一个函数 safe_divide(a, b),当除数为 0 时返回 None 而不是抛出异常。
  2. 2. 创建一个自定义异常 InvalidEmailError,当邮箱格式不正确时抛出(只需检查是否包含 @ 和 .)。
  3. 3. 写一个文件读取函数,处理文件不存在、权限不足、编码错误等异常,返回友好的错误信息。
  4. 4. 实现一个简单的计算器,处理除零错误、无效输入、不支持的运算符等异常。
  5. 5. 创建一个"重试装饰器",当函数抛出异常时自动重试指定次数。

十、小结

本篇我们系统学习了 Python 的异常处理机制:

核心语法

  • • try-except:捕获并处理异常
  • • else:没有异常时执行
  • • finally:无论如何都执行(资源清理)
  • • raise:主动抛出异常

自定义异常

  • • 继承 Exception 类
  • • 命名以 Error 结尾
  • • 提供有意义的错误信息和上下文

最佳实践

  • • EAFP 原则:先做再处理,而不是先检查再做
  • • 具体异常优于宽泛异常:不要滥用 except Exception
  • • 不要忽略异常:至少记录日志
  • • 保持异常粒度适中:一个 try 块不要太长

异常处理是编写健壮程序的关键技能。掌握了它,你的程序就能优雅地应对各种意外情况,而不是一遇到错误就崩溃。

下一篇,我们将学习文件操作——让你的程序能读写数据、处理配置文件、生成报告。不见不散!


本文首发于微信公众号「羽您码上聊」,欢迎关注获取更多技术内容。

最新文章

随机文章