Python 异常处理魔法手册:try - except 的终极艺术
对话实录
小白:(崩溃)我的程序一遇到错误就崩溃,怎么办?
专家:(掏出魔法书)用 try - except,让程序优雅地处理错误!
当我们写的python程序遇到意想不到的问题的时候(比如出现了bug),如果程序不做处理,程序就会异常停止,此时我们可以使用try语句来捕获这些异常,不会使程序异常终止。
异常处理基础三连击
1. 基本用法
在 Python 中,try块用于包含可能会引发异常的代码。当try块中的代码引发异常时,程序流程会立即跳转到对应的except块中进行处理。
try:
num = int(input("请输入数字:"))
print(10 / num)
except ValueError:
print("输入的不是数字!")
except ZeroDivisionError:
print("不能除以0!")
except Exception as e:
print(f"{e}")
在这段代码中,int(input("请输入数字:"))尝试将用户输入转换为整数,如果用户输入的不是数字,就会引发ValueError异常,此时会执行第1个except块中的代码。而10 / num如果num为 0,会引发ZeroDivisionError异常,由第2个except块处理,最后如果不满足上述错误由第3个except模块处理所有错误。
专家提醒:
1.先捕获具体异常,再捕获通用异常!如果先捕获通用异常Exception,那么它会捕获所有异常,导致具体异常的except块永远不会执行。
2.不能只写try语句,最少需要搭配except语句或者finally语句
3.except语句可以写多个
2. else 和 finally
else块在try块没有引发任何异常时执行,finally块无论try块是否有异常,都会执行。
try:
result = 10 / 2
except ZeroDivisionError:
print("除零错误")
else:
print("计算成功:", result) # 无异常时执行
finally:
print("执行完毕") # 无论是否有异常都执行
这里10 / 2不会引发异常,所以else块中的代码会执行,最后finally块也会执行。
三大实战案例
案例 1:文件操作
在文件操作中,可能会遇到文件不存在或者没有权限读取的情况。
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("文件不存在!")
except PermissionError:
print("没有读取权限!")
else:
process(content)
with open("data.txt") as f尝试打开文件,如果文件不存在,会引发FileNotFoundError异常;如果没有权限,会引发PermissionError异常。若成功打开并读取文件,else块中的process(content)会对文件内容进行处理。
案例 2:API 请求
使用requests库进行 API 请求时,可能会遇到请求超时或其他错误。
import requests
try:
response = requests.get("https://api.example.com/data", timeout = 5)
response.raise_for_status() # 检查HTTP错误
except requests.Timeout:
print("请求超时!")
except requests.RequestException as e:
print(f"请求失败:{e}")
else:
print(response.json())
requests.get("
https://api.example.com/data", timeout = 5)尝试发送请求,若 5 秒内未得到响应,会引发requests.Timeout异常。response.raise_for_status()会检查 HTTP 状态码,如果状态码表示请求失败(如 404、500 等),会引发requests.RequestException异常。若请求成功,else块会打印响应的 JSON 数据。
案例 3:数据库操作
使用sqlite3进行数据库操作时,可能会遇到各种数据库相关的错误。
import sqlite3
try:
conn = sqlite3.connect("test.db")
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
except sqlite3.OperationalError as e:
print(f"数据库错误:{e}")
else:
print(cursor.fetchall())
finally:
conn.close()
sqlite3.connect("test.db")尝试连接数据库,如果连接失败或者执行 SQL 语句时出现操作错误,会引发sqlite3.OperationalError异常。若操作成功,else块会打印查询结果,最后无论是否有异常,finally块都会关闭数据库连接。
四大血泪陷阱
捕获所有异常
直接使用except:会捕获所有异常,包括SystemExit等系统级异常,这可能导致程序无法正常退出或出现难以调试的问题。
try:
risky_code()
except: # 捕获所有异常,包括SystemExit
print("出错了")
正确做法是捕获Exception,它捕获所有非系统异常。
try:
risky_code()
except Exception as e: # 捕获所有非系统异常
print(f"错误:{e}")
忽略异常
在except块中直接使用pass会吞掉异常,导致错误信息丢失,难以排查问题。
try:
risky_code()
except: # 吞掉异常
pass
正确做法是记录错误并重新抛出,以便上层调用者处理。
try:
risky_code()
except Exception as e:
log_error(e) # 记录错误
raise # 重新抛出
finally 中的 return
在函数中,如果finally块中有return语句,它会覆盖try块中的return。
def test():
try:
return 1
finally:
return 2 # 覆盖了try中的return
print(test()) # → 2
这通常不是预期的行为,应避免在finally块中使用return,除非确实有特殊需求。
专家工具箱
1. 自定义异常
当 Python 内置的异常类型不能满足需求时,可以自定义异常。
先看下我们平常用到的异常比如AssertionError,SyntaxError,ZeroDivisionError,NameError等,在python的自带文件builtins.py中类似如下定义:
每个异常类继承了父类Exception,看上去定义很简单。
下面我们通过同一个示例来定义一个自定义的异常类,并在程序中抛出异常。
举例:编写一个猜数字的小游戏,输入数字范围为1-100,当输入的数字不在该范围内时会抛出异常;猜对的时候程序停止;猜错了可以继续猜,不限制次数。
1 定义一个输入异常的类
class InputError(Exception):
""" Input failed."""
def __init__(self, *args, **kwargs):
pass
2 编写猜数字游戏,并通过raise语句抛出异常
def guess_number():
#定义数字5
num = 5
while True:
print(f'请输入的数字')
#input函数输入默认为字符串
number = int(input())
print(f'输入的数字为{number}')
if number < 0 or number > 100:
raise InputError
if num == number:
print(f'猜对了数字为{num},游戏结束')
break
else:
print(f'没有猜对奥,请继续猜吧')
3 执行函数guess_number(),当我们在屏幕中输入数字-1或者101时,程序会抛出异常如下
4 通过try捕获自定义的异常
try:
guess_number()
except InputError:
print('输入的数字不能小于0,大于100')
当执行程序并重新输入数字101时,程序捕获到异常并打印如下:
2. 异常链
异常链可以在捕获一个异常后,引发另一个异常,并保留原始异常的信息。
try:
risky_code()
except ValueError as e:
raise RuntimeError("处理失败") from e
这样在捕获ValueError后,引发RuntimeError,并且通过from e保留了ValueError的信息,便于调试。
3. 上下文管理器
上下文管理器通过__enter__和__exit__方法来管理资源的分配和释放,在__exit__方法中可以处理异常。
class SafeOperation:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f"捕获异常:{exc_val}")
return True # 抑制异常
with SafeOperation():
risky_code()
当with块中的risky_code()引发异常时,__exit__方法会捕获并处理异常,return True表示抑制异常,不再向上层抛出。
小白:(献上膝盖)原来异常处理这么强大!
专家:(扶起小白)记住:异常处理不是万能的,但没有它是万万不能的!