一、 Python 异常处理:try-except
、else
、finally
与自定义异常
在程序运行时,可能会遇到错误(如输入值错误、文件不存在、等)。Python 提供了异常处理机制,可以优雅地捕获这些错误,避免程序崩溃。本文将介绍如何使用 try-except
捕获异常、else
和 finally
的用法,并讲解如何自定义异常。
1. 使用 try-except
捕获异常
try-except
块用于捕获代码中的错误,并在出现错误时执行特定操作。
基本用法
try:
num = int(input("请输入一个数字: ")) # 输入不是数字可能抛出 ValueError
print(f"你输入的数字是: {num}")
except ValueError:
print("输入无效,请输入一个有效的数字!")
try
:尝试执行这段代码。except
:当出现指定的异常时执行这里的代码。
捕获多种异常
try:
# 输入0或非数字,则会发生异常.
result = 10 / int(input("输入一个非零整数: "))
except ValueError:
print("输入无效,请输入一个整数。")
except ZeroDivisionError:
print("除数不能为 0!")
2. else
和 finally
的用法
else
:try
代码块没有抛出异常时执行。finally
:无论是否出现异常,都执行的代码块(关闭和释放资源通常在这里进行.)。
示例:else
和 finally
try:
# with ... as f: 块结束自动关闭资源.
with open("data.txt", "r") as f:
content = f.read()
except FileNotFoundError:
print("未找到该文件!")
else:
print(f"文件读取成功:{content}")
finally:
print("操作结束。")
else
:如果try
代码块没有抛出异常,就会执行else
中的代码。finally
:无论文件是否存在,finally
中的代码都会执行。
3. 捕获所有异常
使用 Exception
可以捕获所有类型的异常,但应谨慎使用,避免掩盖潜在错误。
try:
x = 10 / 0
except Exception as e:
print(f"出现异常:{e}")
4. 自定义异常
有时候内置异常不够满足需求,可以自定义异常类,用于抛出特定场景下的错误。
自定义异常类
class AgeError(Exception): # 继承 Exception
"""自定义异常:年龄错误"""
pass
使用自定义异常
def check_age(age):
if age < 0:
raise AgeError("年龄不能为负数!") # 抛出异常
else:
print(f"年龄是:{age}")
try:
check_age(-5)
except AgeError as e:
print(f"捕获到自定义异常:{e}")
raise
:用于主动抛出异常。- 自定义异常类:继承自
Exception
,并实现自己的功能。
5. 常见陷阱与最佳实践
- 不要滥用捕获所有异常:避免使用
except Exception
捕获所有异常,这会让你忽略一些潜在的逻辑错误。 - 在合适的位置使用
finally
:确保资源(如文件、网络连接)在任何情况下都能正确关闭。 - 自定义异常需具体:自定义异常时应尽量清晰描述错误类型,避免泛用。
二、Python 异常处理的高级用法
在掌握了基本的 try-except
语法后,Python 的异常处理还提供了更多高级特性,包括链式异常、上下文管理、重新抛出异常、自定义异常类的优化等。
1. 链式异常 (raise ... from ...
)
Python 支持将一个异常与其根本原因关联起来,这叫链式异常。这在捕获某异常后,如果需要抛出另一个新的异常
时非常有用。
示例:链式异常
try:
result = 100 / 0
except ZeroDivisionError as e:
raise ValueError("计算失败") from e # 关联原始异常
解释:
- 这里捕获了
ZeroDivisionError
,并抛出新的ValueError
。 from e
关联了两个异常,便于追踪错误的根源。
输出:
result = 100 / 0
~~~~^~~
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
......
ValueError: 计算失败
2. 重新抛出异常 (raise
)
在某些情况下,我们希望捕获异常后执行某些操作,但仍然将原始异常继续抛出。
示例:重新抛出异常
try:
result = 10 / 0
except ZeroDivisionError:
print("出现/0的异常, 但是希望继续抛出给后面处理")
raise # 重新抛出异常,让调用者处理
解释:
raise
没有参数时,会重新抛出当前捕获的异常。- 当前只进行log输出,再抛出原始异常让后续处理比较常用.
3. 自定义异常类的优化
我们可以创建更复杂的自定义异常,并支持自定义的错误信息和上下文数据。
示例:自定义异常类
class InvalidAgeError(Exception):
"""
自定义异常:非法年龄.
可以设定一些初始化时候参数.
"""
def __init__(self, age, message="年龄不合法"):
self.age = age
self.message = message
super().__init__(f"{message}: {age}")
# 使用自定义异常
def validate_age(age):
if age < 0 or age > 150:
raise InvalidAgeError(age) # 参数传入错误的年龄.
try:
validate_age(200)
except InvalidAgeError as e:
print(f"年龄无效异常: {e}")
解释:
- 自定义异常类可以设定包含一些信息,如非法的年龄值
age
。 - 错误发生后的上下文更加清晰,方便排查。
4. 捕获并分类多个异常
在复杂的场景中,可能会出现多种不同类型的异常。你可以使用元组捕获多个异常类型。
示例:捕获多个异常
try:
num = int(input("输入一个整数: "))
result = 10 / num
except (ValueError, ZeroDivisionError) as e:
print(f"捕获异常: {e}")
解释:
- 使用
(ValueError, ZeroDivisionError)
可以一次捕获多个异常类型。 - 捕获的异常会存储在变量
e
中。
5. 自定义上下文管理器处理异常
通过实现 __enter__
和 __exit__
方法,你可以创建自定义的上下文管理器,用于自动处理异常。
示例:自定义上下文管理器
class HelloWorldResource:
# 和 with ... as x : 结合使用, 进入块时调用.
def __enter__(self):
print("你好世界: 资源已打开")
return self
# 和 with ... as x : 结合使用, 退出块时调用
def __exit__(self, exc_type, exc_value, traceback):
if exc_type: # 异常存在则可以在这里面处理
print(f"捕获到世界BUG: {exc_value}")
print("你好世界: 资源已关闭")
return True # 返回 True 表示异常已处理
# 使用自定义上下文管理器
with HelloWorldResource() as resource:
raise ValueError("空间发生崩溃") # 触发异常
解释:
- 如果
with
语句中的代码抛出异常,__exit__
方法会捕获它。 - 返回
True
表示异常已被处理,不会再向上传播。
执行结果
你好世界: 资源已打开
捕获到世界BUG: 空间发生崩溃
你好世界: 资源已关闭
6. 上下文变量传播异常
在某些复杂系统中,你可以将异常与上下文变量关联,以便在日志中提供更多调试信息。
示例:通过日志记录上下文信息
import logging
# 设定日志输出级别
logging.basicConfig(level=logging.INFO)
try:
raise ValueError("这个值好像不对呀!!!")
except ValueError as e:
logging.exception("我是日志, 我说这个值不太对劲!!!")
解释:
- 使用
logging.exception()
可以将堆栈信息记录在日志中,便于排查问题。
总结
学习小笔记
try-except
:用于捕获代码中的异常,防止程序崩溃。else
:当try
代码块没有异常时执行。finally
:无论是否出现异常都会执行,常用于资源清理。- 自定义异常:根据需要创建新的异常类型,用于抛出特定错误。
通过异常处理,我们可以让程序在面对错误时更加稳定、可控,在代码中优雅处理异常问题!