Python异常处理


在Python中,异常处理是应对程序运行时错误的核心机制,断言是调试阶段的条件检查工具,引发异常则允许我们主动抛出自定义或内置异常。本文将从基础到进阶,结合实例详细讲解这三部分内容。

一、Python异常处理

异常是程序运行时发生的非预期错误(如除零、文件不存在、类型错误等),若不处理会导致程序崩溃。Python通过try-except-finally-else语句实现异常捕获和处理。

1. 异常的基本概念

Python内置了大量异常类,常见的有: - ZeroDivisionError:除零错误 - TypeError:类型不匹配 - ValueError:值无效 - FileNotFoundError:文件未找到 - IndexError:索引越界 - KeyError:字典键不存在

所有内置异常均继承自BaseException,核心业务异常通常继承自Exception(开发中主要处理此类)。

2. 异常处理的核心语法

Python提供了4个关键字实现异常处理,组合使用可覆盖绝大多数场景。

(1)基础:try-except

捕获指定异常并处理,语法:

try:
    # 可能引发异常的代码块
    risky_code
except 异常类型1 [as 变量名]:
    # 处理异常类型1的逻辑
except 异常类型2 [as 变量名]:
    # 处理异常类型2的逻辑

实例1:捕获单一异常

try:
    result = 10 / 0  # 除零操作,触发ZeroDivisionError
except ZeroDivisionError as e:
    print(f"异常发生:{e}")  # 输出:异常发生:division by zero

实例2:捕获多个异常

try:
    num = int("abc")  # 类型转换失败,触发ValueError
    # result = 10 / 0  # 若打开此行,触发ZeroDivisionError
except ZeroDivisionError as e:
    print(f"除零错误:{e}")
except ValueError as e:
    print(f"值错误:{e}")  # 输出:值错误:invalid literal for int() with base 10: 'abc'

(2)捕获所有异常

若不确定可能的异常类型,可使用except Exception as e捕获所有业务异常(不建议用裸except:,会捕获KeyboardInterrupt等系统异常,导致程序无法正常退出)。

try:
    lst = [1, 2, 3]
    print(lst[5])  # 索引越界,触发IndexError
except Exception as e:
    print(f"捕获到异常:{type(e).__name__},信息:{e}")  # 输出:捕获到异常:IndexError,信息:list index out of range

(3)else子句:无异常时执行

try块未触发任何异常,会执行else子句的代码(可选)。

try:
    result = 10 / 2
except ZeroDivisionError as e:
    print(f"异常:{e}")
else:
    print(f"计算成功,结果:{result}")  # 输出:计算成功,结果:5.0

(4)finally子句:始终执行

无论try块是否触发异常,finally子句的代码都会执行,常用于资源清理(如关闭文件、数据库连接)。

try:
    f = open("test.txt", "r")  # 尝试打开不存在的文件
    content = f.read()
except FileNotFoundError as e:
    print(f"异常:{e}")
finally:
    # 无论是否异常,都关闭文件(若文件对象存在)
    if 'f' in locals():
        f.close()
        print("文件已关闭")
print("程序继续执行")

3. 异常处理的嵌套

可在exceptfinally块中嵌套try-except,处理嵌套代码的异常。

try:
    result = 10 / 2
    try:
        lst = [1, 2, 3]
        print(lst[5])
    except IndexError as e:
        print(f"内层异常:{e}")
except ZeroDivisionError as e:
    print(f"外层异常:{e}")
finally:
    print("嵌套异常处理完成")

二、断言(Assert)

断言是Python的调试工具,通过assert语句检查某个条件是否为True,若为False则抛出AssertionError异常。仅用于调试阶段,不建议用于生产环境的业务逻辑验证。

1. 断言的语法

assert 条件表达式 [, 异常提示信息]
  • 条件表达式True,程序继续执行;
  • 若为False,触发AssertionError,并输出可选的提示信息。

2. 断言的使用实例

实例1:检查输入值的范围

age = -5
# 断言年龄必须大于0,否则抛出异常
assert age > 0, "年龄必须是正整数"

运行结果:

AssertionError: 年龄必须是正整数

实例2:调试时检查函数参数

def calculate_average(scores):
    # 断言分数列表非空
    assert len(scores) > 0, "分数列表不能为空"
    return sum(scores) / len(scores)

# 测试
print(calculate_average([]))  # 触发AssertionError

3. 断言的注意事项

  • 断言可被关闭:运行Python程序时加-O参数(优化模式),会忽略所有assert语句,因此不要用断言验证用户输入
  • 仅用于调试:断言的作用是“确认程序的内部状态符合预期”,而非处理业务异常(如参数校验应使用if+raise)。

三、引发异常(Raise)

除了Python自动抛出的异常,我们还可以通过raise语句主动引发异常,用于自定义错误场景(如参数不符合要求、业务规则被违反等)。

1. 引发内置异常

直接通过raise抛出Python内置异常,可指定异常提示信息。

语法1:仅抛出异常类

raise 异常类型

语法2:抛出异常实例(带提示信息)

raise 异常类型("自定义提示信息")

实例1:主动抛出值错误

num = int(input("请输入1-10的数字:"))
if num < 1 or num > 10:
    # 主动抛出ValueError
    raise ValueError(f"输入的数字{num}超出1-10的范围")
print(f"你输入的数字是:{num}")

实例2:重新引发异常except块中,可通过raise重新抛出捕获的异常(用于异常透传或补充信息)。

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print("记录异常日志:除零错误")
    raise  # 重新抛出原异常,不修改异常信息

2. 自定义异常类

当Python内置异常无法满足业务需求时,可自定义异常类,必须继承自Exception(而非BaseException)。

自定义异常的语法

class 自定义异常类名(Exception):
    """异常说明文档"""
    # 可自定义初始化方法或其他方法
    def __init__(self, message):
        self.message = message
        super().__init__(self.message)

实例:自定义业务异常

# 定义自定义异常:分数超出范围异常
class ScoreOutOfRangeError(Exception):
    """当分数不在0-100范围内时触发的异常"""
    def __init__(self, score):
        self.score = score
        super().__init__(f"分数{score}超出0-100的范围")

# 使用自定义异常
def check_score(score):
    if not (0 <= score <= 100):
        raise ScoreOutOfRangeError(score)
    print(f"分数{score}验证通过")

# 测试
try:
    check_score(105)
except ScoreOutOfRangeError as e:
    print(f"捕获到自定义异常:{e}")  # 输出:捕获到自定义异常:分数105超出0-100的范围

四、综合实战案例

结合异常处理断言引发异常实现一个成绩管理程序,功能包括:输入学生成绩、验证成绩范围、计算平均分。

# 自定义异常:空列表异常
class EmptyScoreListError(Exception):
    """当分数列表为空时触发的异常"""
    pass

def input_scores():
    """输入学生成绩,返回成绩列表"""
    scores = []
    while True:
        s = input("请输入学生成绩(输入q结束):")
        if s.lower() == 'q':
            break
        try:
            score = float(s)
            # 主动抛出异常:成绩超出范围
            if not (0 <= score <= 100):
                raise ValueError(f"成绩{score}无效,必须在0-100之间")
            scores.append(score)
        except ValueError as e:
            print(f"输入错误:{e}")
            continue
    return scores

def calculate_average(scores):
    """计算平均分,使用断言和自定义异常"""
    # 断言:分数为数字类型(调试用)
    for score in scores:
        assert isinstance(score, (int, float)), "分数必须是数字"
    # 主动抛出自定义异常:空列表
    if len(scores) == 0:
        raise EmptyScoreListError("分数列表为空,无法计算平均分")
    return sum(scores) / len(scores)

# 主程序
if __name__ == "__main__":
    try:
        scores = input_scores()
        avg = calculate_average(scores)
    except EmptyScoreListError as e:
        print(f"程序异常:{e}")
    else:
        print(f"学生成绩平均分:{avg:.2f}")
    finally:
        print("成绩计算程序执行完毕")

五、注意事项

  1. 避免裸except:不要使用except:捕获所有异常,会导致无法中断程序(如Ctrl+CKeyboardInterrupt),应使用except Exception as e
  2. 断言的局限性:断言仅用于调试,生产环境需用if+raise验证业务逻辑;
  3. 自定义异常规范:自定义异常类名以Error结尾,继承自Exception,并添加清晰的提示信息;
  4. finally的使用finally中避免返回值,否则会覆盖try/except中的返回值;
  5. 异常的粒度:捕获异常时应尽量指定具体的异常类型,而非笼统的Exception