Python Day22 异常处理 及 例题解析
一、异常基础
-
定义:程序在编写或运行期间产生的错误。
- 编写时的异常(如语法错误):必须解决。
- 运行时的异常(多由数据导致):可处理也可不处理。
-
异常类体系:
BaseException:所有异常的父类。- 常用子类:
Exception(多数异常基于此类)。
-
常见异常类型:
SyntaxError:语法错误(编写时)。NameError:标识符错误(变量未定义等)。TypeError:类型错误(运算时传入类型不匹配)。KeyError:键错误(字典中键不存在)。ValueError:值错误(值不符合预期,如int('a'))。IndexError:索引错误(列表索引越界)。ZeroDivisionError:除数为 0 错误。ArithmeticError:算术错误(ZeroDivisionError的父类)。StopIteration:迭代器异常(迭代结束后继续访问)。RecursionError:递归异常(递归深度超限)。
二、异常传播机制
- 当程序产生异常时,默认将异常抛给上层调用者,并终止当前程序。
- 若上层未处理,继续向上传播,直至 Python 解析器:解析器输出异常栈(先进后出)并强制终止程序。
- 若上层处理了异常,则异常不再传播,程序可继续执行。
示例:
def a():
print("-----a-----")
b() # 调用b,若b出错则抛给a
print("-----a结束-----")
def b():
print("-----b-----")
print(3/0) # 产生ZeroDivisionError
print("-----b结束-----")
a() # 异常从b传到a,再传到解析器,程序终止
三、异常处理机制(try-except)
用于捕获并处理可能产生异常的代码,避免程序终止。
基本语法:
try:
# 可能产生异常的代码
except 异常类型1 as e:
# 处理异常类型1的逻辑(e为异常对象,可获取原因)
except 异常类型2 as e:
# 处理异常类型2的逻辑
...
扩展用法:
-
捕获多种异常(同处理逻辑):用元组包含多个异常类型。
try: a = int(input("输入数字:")) b = int(input("输入数字:")) print(a/b) except (ValueError, ZeroDivisionError) as e: print(f"异常已处理:{e}") # 同时处理值错误和除0错误 -
捕获所有异常:
- 用
Exception(推荐,覆盖多数异常)或BaseException。 - 若省略异常类型,可处理所有异常,但无法直接获取异常原因(需通过
sys.exception()或traceback)。
- 用
-
保留异常栈信息:使用
traceback.print_exc()打印详细异常栈。from traceback import print_exc try: 1/0 except: print_exc() # 输出完整异常栈
四、try-except-else
else块:当try中代码无异常时执行。
示例:
try:
a = int(input("数字1:"))
b = int(input("数字2:"))
print(a/b)
except (ValueError, ZeroDivisionError) as e:
print(f"异常:{e}")
else:
print("程序执行完毕(无异常)") # 无异常时执行
五、try-except-finally
finally块:无论try中是否产生异常,一定会执行(前提是try代码被执行)。- 用途:释放资源(如关闭文件、数据库连接)。
示例:
f = None
try:
f = open("file.txt")
f.read()
except:
print("读取错误")
finally:
if f: # 确保文件关闭
f.close()
- 特殊情况:
finally中修改返回值的影响(引用类型会生效,值类型不生效):def test1(): a = [10] try: return a finally: a[0] += 10 # 列表是引用类型,返回值会变为[20] def test2(): a = 10 try: return a finally: a += 10 # 整数是值类型,返回值仍为10
六、自定义异常类
- 用途:包装官方异常或定义业务特定异常。
- 实现:继承已知异常类(推荐
Exception)。
示例:
class ServiceError(Exception):
"""业务异常类"""
pass
class QikuException(Exception):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # 调用父类构造
# 使用:捕获官方异常后抛出自定义异常
try:
3/0
except ZeroDivisionError as e:
raise ServiceError(f"业务错误:{e}") # 抛出ServiceError
七、断言(assert)
- 语法:
assert 条件, 消息 - 逻辑:若条件为
True,程序继续;若为False,抛出AssertionError,异常信息为 “消息”。
示例:
b = 10 / 3
assert isinstance(b, int), "b必须是整数" # 条件为False,抛出AssertionError
八、日志模块(logging)
- 作用:记录系统运行信息,用于追踪和排查问题。
- 可根据环境配置日志级别(开发环境多日志,生产环境少日志)。
1. 日志级别(从低到高)
DEBUG:调试信息(级别最低,输出最多,包含所有级别)。INFO:通知信息(业务流程日志,输出INFO及以上级别)。WARNING:警告信息(输出WARNING及以上级别)。ERROR:错误信息(级别最高,仅输出ERROR)。
2. 输出目的地
- 控制台(console)、文件(file)、邮件(email)、数据库(db)等。
3. 输出格式参数
%(levelname)s:日志级别。%(asctime)s:日志产生时间。%(pathname)s:调用日志的源文件完整路径。%(filename)s:调用日志的源文件名。%(lineno)d:调用日志的源代码行号。%(message)s:日志消息内容。
一、异常处理练习题
1. 数字转换异常处理
题目:编写程序,要求用户输入一个数字并转换为整数。若输入非数字,捕获ValueError并打印错误消息。
解答:
try:
a = int(input("输入一个数字:"))
except ValueError as e:
print("输入的不是数字", str(e))
2. 列表索引异常处理
题目:创建含数字的列表,尝试访问索引为 10 的元素。捕获IndexError并打印适当消息。
解答:
try:
ls = [1, 2, 3, 4, 5, 6, 7]
print(ls[10])
except IndexError as e:
print('未找到该索引位置对应的值', str(e))
3. 除法除零异常处理
题目:要求用户输入两个数字并相除。捕获ZeroDivisionError,若发生则提醒 “不能除以零”。
解答:
from traceback import print_exc
try:
a = int(input('请输入被除数:'))
b = int(input('请输入除数:'))
c = a / b
except ZeroDivisionError as e:
print_exc() # 打印异常栈
print('除数不能为0')
4. 列表元素转整数异常处理
题目:创建含字符串的列表,尝试将每个元素转为整数。捕获ValueError并打印错误消息。
解答:
from traceback import print_exc
try:
ls = [1, 2, 'string', 2, 3, '122']
ls = [int(x) for x in ls] # 列表推导式转换
except ValueError as e:
print_exc() # 打印异常栈
print('存在不能转化成整数的类型!')
5. 多异常(索引 + 键)处理
题目:创建含字典的列表,尝试访问索引为 5 的元素的键 "name"。捕获IndexError和KeyError,分别打印消息。
解答:
from traceback import print_exc
try:
ls = [1, 2, 3, 4, 5, 6, {'age': 12}]
print(ls[10]) # 可能触发IndexError
print(ls[5]['name']) # 可能触发KeyError
except (IndexError, KeyError) as e:
print(f"发生错误: {type(e)}")
print(f"错误详情: {e}")
6. 索引类型异常处理
题目:创建含数字的列表,尝试访问索引为 "abc" 的元素。捕获TypeError并打印消息。
解答:
try:
ls = [1, 2, 3, 4, 5, 6, 'abcd']
print(ls['abc']) # 字符串索引非法(列表索引需为整数)
except TypeError as e:
print('索引格式错误', str(e))
7. 平方根计算异常处理
题目:要求用户输入数字并计算平方根。捕获ValueError(非数字输入)和ArithmeticError(负数开方),分别打印消息。
解答:
try:
a = int(input("请输入一个数字:"))
if a < 0:
raise ArithmeticError("负数不能开方") # 主动抛出算术异常
print(a ** 0.5)
except ValueError as e:
print(f"输入的不是一个数字, {e}")
except ArithmeticError as e:
print(f"算数异常,{e}")
8. 倒数计算异常处理
题目:创建含整数的列表,尝试计算每个元素的倒数。捕获ZeroDivisionError并提醒 “不能除以零”。
解答:
from traceback import print_exc
try:
ls = [0, 100, 200, 300, 500, 10, 20, 1]
ls = [1 / x for x in ls] # 列表推导式计算倒数
except ZeroDivisionError:
print_exc() # 打印异常栈
print('不可以除以0!!')
9. 带重试机制的网页内容获取
题目:编写open_url函数,接收网址并获取文本内容(用requests库)。要求:读取时若产生错误,最多重试 3 次。
解答:
import requests
def open_url(url: str, headers: dict, retry: int = 3):
"""获取指定网址返回的文本内容,最多重试retry次"""
i = 0
while i < retry:
try:
response = requests.get(url, headers=headers)
text = response.text
print(text) # 打印网页内容
return text
except:
i += 1
print(f'第{i}次出现错误') # 记录重试次数
return None # 重试耗尽仍失败,返回None
if __name__ == '__main__':
url = "https://www.example.com"
headers = {"User-Agent": "Mozilla/5.0"} # 模拟浏览器请求头
result = open_url(url, headers)
二、令牌生成与验证问题
题目要求
-
生成令牌函数
generator_remember_token(username, password, key, duration):- 令牌规则:
base64(<username>:<timestamp>:md5(<username>:<timestamp>:<key>:<password>)) - 其中
timestamp为令牌过期时间戳(当前时间 + 有效时长duration,精确到秒)。
- 令牌规则:
-
验证令牌函数
check_remember_token(token, username, password, key):- 验证规则:
- 令牌中的用户名必须与传入的
username一致; - 验证令牌中的 MD5 值,确保密码匹配;
- 检查时间戳是否过期(当前时间≤过期时间戳)。
- 令牌中的用户名必须与传入的
- 验证规则:
解答代码
import base64
import hashlib
import time
def generator_remember_token(username, password, key, duration):
"""生成令牌:按规则编码用户名、过期时间戳和加密信息"""
now = int(time.time()) # 当前时间戳(秒)
timestamp = now + duration # 过期时间戳 = 当前时间 + 有效时长
# 生成MD5加密串:拼接用户名、时间戳、密钥、密码后加密
md5_str = hashlib.md5(f"{username}:{timestamp}:{key}:{password}".encode()).hexdigest()
# 拼接用户名、时间戳、MD5串,再Base64编码
token_str = f"{username}:{timestamp}:{md5_str}"
token = base64.b64encode(token_str.encode()) # Base64编码(字节类型)
return token
def check_remember_token(token, username, password, key):
"""验证令牌:检查用户名、密码匹配性和时效性"""
# 解码令牌:Base64解码后转字符串
token_decoded = base64.b64decode(token).decode()
# 拆分令牌为:用户名、时间戳、MD5串
parts = token_decoded.split(':')
if len(parts) != 3:
raise Exception("令牌格式错误") # 格式异常处理
token_username, token_timestamp, token_md5 = parts
current_time = int(time.time()) # 当前时间戳
# 1. 验证用户名一致
if token_username != username:
raise Exception('用户名错误!')
# 2. 验证时间戳未过期
if current_time > int(token_timestamp):
raise Exception('令牌已过期!')
# 3. 验证MD5值(密码和密钥匹配)
expected_md5 = hashlib.md5(f"{username}:{token_timestamp}:{key}:{password}".encode()).hexdigest()
if expected_md5 != token_md5:
raise Exception("密码或密钥不匹配")
return True # 所有验证通过
if __name__ == "__main__":
# 示例测试
username = "user1"
password = "password123"
key = "secretkey"
duration = 5 # 令牌有效时长5秒
# 生成令牌
token = generator_remember_token(username, password, key, duration)
print(f"生成的令牌: {token}")
# 等待10秒(超过有效时长,测试过期逻辑)
time.sleep(10)
# 验证令牌
try:
is_valid = check_remember_token(token, username, password, key)
print(f"令牌有效: {is_valid}")
except Exception as e:
print(f"验证失败: {e}") # 预期输出"验证失败: 令牌已过期!"
更多推荐
所有评论(0)