Python 文件和异常练习题(含答案)
1. 创建并读取文件
题目: 编写程序完成以下操作:
- 将下面的内容写入文件
greeting.txt - 重新读取文件,打印全部内容
- 再次读取文件,打印总行数
你好,Python!
学习编程很有趣。
坚持下去,一定能成功!
参考答案:
# 第一步:写入文件
content = "你好,Python!n学习编程很有趣。n坚持下去,一定能成功!n"
with open("greeting.txt", "w", encoding="utf-8") as f:
f.write(content)
print("文件写入成功!")
# 第二步:读取全部内容
with open("greeting.txt", "r", encoding="utf-8") as f:
text = f.read()
print("文件内容:")
print(text)
# 第三步:统计行数
with open("greeting.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
print(f"共 {len(lines)} 行")
输出:
文件写入成功!
文件内容:
你好,Python!
学习编程很有趣。
坚持下去,一定能成功!
共 3 行
关键点:
"w"模式写入,"r"模式读取,始终加encoding="utf-8"readlines()返回列表,每个元素是一行(含n),len()可得行数
2. 逐行读取并处理
题目: 创建文件 scores.txt,内容如下(每行一个分数):
85
92
67
78
95
43
88
编写程序读取该文件,计算并打印:最高分、最低分、平均分(保留1位小数)。
参考答案:
# 先创建文件
with open("scores.txt", "w", encoding="utf-8") as f:
f.write("85n92n67n78n95n43n88n")
# 读取并计算
scores = []
with open("scores.txt", "r", encoding="utf-8") as f:
for line in f: # 逐行读取,内存友好
line = line.strip() # 去掉换行符和空格
if line: # 跳过空行
scores.append(int(line)) # 转成整数
print(f"共 {len(scores)} 个分数:{scores}")
print(f"最高分:{max(scores)}")
print(f"最低分:{min(scores)}")
print(f"平均分:{sum(scores) / len(scores):.1f}")
输出:
共 7 个分数:[85, 92, 67, 78, 95, 43, 88]
最高分:95
最低分:43
平均分:78.3
关键点:
line.strip():同时去掉首尾空格和换行符if line::跳过空行,避免int("")报错
3. 追加写入文件
题目: 模拟一个简单的日志系统:
- 第一次运行时,写入”日志系统启动”
- 之后三次调用
add_log(message)函数,每次追加一条带序号的日志 - 最终读取并打印所有日志
参考答案:
LOG_FILE = "app.log"
# 初始化日志文件(覆盖写入)
with open(LOG_FILE, "w", encoding="utf-8") as f:
f.write("=== 日志系统启动 ===n")
def add_log(message, count=[0]):
"""追加一条日志(count 用列表保存状态,每次调用自增)"""
count[0] += 1
log_line = f"[{count[0]:03d}] {message}n"
with open(LOG_FILE, "a", encoding="utf-8") as f: # "a" 追加模式
f.write(log_line)
print(f"日志已记录:{log_line.strip()}")
# 追加三条日志
add_log("用户张三登录成功")
add_log("查询了订单列表")
add_log("用户张三退出登录")
# 读取所有日志
print("n=== 完整日志 ===")
with open(LOG_FILE, "r", encoding="utf-8") as f:
print(f.read())
输出:
日志已记录:[001] 用户张三登录成功
日志已记录:[002] 查询了订单列表
日志已记录:[003] 用户张三退出登录
=== 完整日志 ===
=== 日志系统启动 ===
[001] 用户张三登录成功
[002] 查询了订单列表
[003] 用户张三退出登录
关键点: "w" 初始化(清空),"a" 追加(保留原内容)。两者混淆会导致数据丢失。
4. 捕获文件不存在异常
题目: 编写函数 read_file(filename),尝试读取文件并返回内容:
- 文件不存在时:打印友好提示,返回
None - 编码错误时:提示用户可能不是 UTF-8 文件,返回
None - 成功时:返回文件内容字符串
测试:分别传入一个存在的文件名和一个不存在的文件名。
参考答案:
def read_file(filename):
"""安全读取文件,失败时返回 None"""
try:
with open(filename, "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
print(f"[提示] 文件 '{filename}' 不存在,请检查路径是否正确。")
return None
except UnicodeDecodeError:
print(f"[提示] '{filename}' 可能不是 UTF-8 编码,请转换编码后再试。")
return None
else:
# 只有 try 正常执行完才会进入 else
print(f"[成功] 已读取文件 '{filename}',共 {len(content)} 个字符。")
return content
# 先创建一个测试文件
with open("test.txt", "w", encoding="utf-8") as f:
f.write("这是测试内容。n你好!")
# 测试
result1 = read_file("test.txt") # 存在的文件
result2 = read_file("not_exist.txt") # 不存在的文件
print("n读取结果:")
print(f"result1 = {repr(result1)}")
print(f"result2 = {repr(result2)}")
输出:
[成功] 已读取文件 'test.txt',共 13 个字符。
[提示] 文件 'not_exist.txt' 不存在,请检查路径是否正确。
读取结果:
result1 = '这是测试内容。n你好!'
result2 = None
关键点:
except FileNotFoundError:专门处理文件不存在except UnicodeDecodeError:专门处理编码错误else块:只有try完全成功时才执行,比把成功代码放在try里更清晰
5. try / except / else / finally 完整结构
题目: 编写函数 safe_divide_from_file(filename):
- 从文件中读取一行,格式为
"数字1 / 数字2"(如"10 / 2") - 解析并计算除法结果
- 使用
try/except/else/finally完整结构处理所有可能的异常 finally块打印”操作结束”
参考答案:
def safe_divide_from_file(filename):
try:
with open(filename, "r", encoding="utf-8") as f:
line = f.readline().strip() # 读取第一行
parts = line.split("/") # 按 / 分割
a = float(parts[0].strip())
b = float(parts[1].strip())
result = a / b
except FileNotFoundError:
print(f"[错误] 文件 '{filename}' 不存在")
return None
except (ValueError, IndexError):
print(f"[错误] 文件格式不正确,应为 '数字1 / 数字2',实际内容:'{line}'")
return None
except ZeroDivisionError:
print(f"[错误] 除数为零,无法计算")
return None
else:
print(f"[成功] {a} / {b} = {result:.4f}")
return result
finally:
print("操作结束") # 无论如何都执行
# 测试 1:正常情况
with open("math.txt", "w", encoding="utf-8") as f:
f.write("10 / 4")
safe_divide_from_file("math.txt")
print()
# 测试 2:除数为零
with open("math_zero.txt", "w", encoding="utf-8") as f:
f.write("10 / 0")
safe_divide_from_file("math_zero.txt")
print()
# 测试 3:文件不存在
safe_divide_from_file("no_file.txt")
输出:
[成功] 10.0 / 4.0 = 2.5000
操作结束
[错误] 除数为零,无法计算
操作结束
[错误] 文件 'no_file.txt' 不存在
操作结束
关键点: finally 块无论是否发生异常、是否有 return,都会执行。
6. 主动触发异常(raise)
题目: 编写函数 create_user(username, age, email):
username:长度必须在 3~20 个字符之间,否则raise ValueErrorage:必须在 1~120 之间,否则raise ValueErroremail:必须包含@,否则raise ValueError
测试函数,分别传入合法和非法的参数。
参考答案:
def create_user(username, age, email):
"""创建用户,对参数做合法性校验"""
# 校验用户名
if not (3 <= len(username) <= 20):
raise ValueError(f"用户名长度应在 3~20 个字符之间,当前长度:{len(username)}")
# 校验年龄
if not (1 <= age <= 120):
raise ValueError(f"年龄应在 1~120 之间,当前值:{age}")
# 校验邮箱
if "@" not in email:
raise ValueError(f"邮箱格式不正确,应包含 '@',当前值:'{email}'")
# 通过所有校验
user = {"username": username, "age": age, "email": email}
print(f"用户创建成功:{user}")
return user
# 测试
test_cases = [
("张三", 25, "zs@example.com"), # 合法
("ab", 25, "zs@example.com"), # 用户名太短
("张三", -5, "zs@example.com"), # 年龄非法
("张三", 25, "not_an_email"), # 邮箱格式错误
]
for username, age, email in test_cases:
try:
create_user(username, age, email)
except ValueError as e:
print(f"创建失败:{e}")
输出:
用户创建成功:{'username': '张三', 'age': 25, 'email': 'zs@example.com'}
创建失败:用户名长度应在 3~20 个字符之间,当前长度:2
创建失败:年龄应在 1~120 之间,当前值:-5
创建失败:邮箱格式不正确,应包含 '@',当前值:'not_an_email'
关键点: raise ValueError("描述信息") 主动触发异常,让调用者知道哪里出了问题,而不是让程序用错误数据继续执行。
7. 自定义异常类
题目: 为一个购物车系统定义两个自定义异常:
OutOfStockError(库存不足):携带商品名和当前库存量InvalidQuantityError(数量非法):携带传入的非法数量
编写 add_to_cart(product, stock, quantity) 函数使用这两个异常,并测试。
参考答案:
class OutOfStockError(Exception):
"""库存不足异常"""
def __init__(self, product, stock):
self.product = product
self.stock = stock
super().__init__(f"商品 '{product}' 库存不足,当前库存:{stock} 件")
class InvalidQuantityError(Exception):
"""数量非法异常"""
def __init__(self, quantity):
self.quantity = quantity
super().__init__(f"购买数量不合法:{quantity},必须是大于 0 的整数")
def add_to_cart(product, stock, quantity):
"""将商品添加到购物车"""
if not isinstance(quantity, int) or quantity <= 0:
raise InvalidQuantityError(quantity)
if quantity > stock:
raise OutOfStockError(product, stock)
print(f"成功将 {quantity} 件 '{product}' 加入购物车!(剩余库存:{stock - quantity})")
# 测试
test_cases = [
("苹果", 50, 3), # 正常
("香蕉", 5, 10), # 库存不足
("橙子", 20, -1), # 数量非法
("葡萄", 30, 0), # 数量非法(0也不行)
]
for product, stock, qty in test_cases:
try:
add_to_cart(product, stock, qty)
except OutOfStockError as e:
print(f"[库存不足] {e},您要买 {qty} 件")
except InvalidQuantityError as e:
print(f"[数量错误] {e}")
输出:
成功将 3 件 '苹果' 加入购物车!(剩余库存:47)
[库存不足] 商品 '香蕉' 库存不足,当前库存:5 件,您要买 10 件
[数量错误] 购买数量不合法:-1,必须是大于 0 的整数
[数量错误] 购买数量不合法:0,必须是大于 0 的整数
关键点: 自定义异常继承 Exception,在 __init__ 里保存有用的属性,并用 super().__init__(message) 设置错误消息。
8. 多级异常处理(找错题)
题目: 下面的代码有 3 处问题,找出并修复:
# 问题代码
def load_config(filename):
f = open(filename, encoding="utf-8") # 问题1
try:
content = f.read()
config = {}
for line in content.split("n"):
key, value = line.split("=") # 问题2:如果某行没有 = 号怎么办?
config[key.strip()] = value.strip()
return config
except: # 问题3
return {}
result = load_config("config.txt")
print(result)
参考答案(含问题说明):
# 问题1:open() 没有用 with 语句,发生异常时文件不会被关闭
# 问题2:如果某行没有 "=",split("=") 返回只有一个元素的列表,
# 解包给两个变量会报 ValueError,但没有处理这个情况
# 问题3:裸 except 太宽泛,连 KeyboardInterrupt、SystemExit 都会捕获,
# 应该用 except Exception 或更具体的类型
def load_config(filename):
try:
with open(filename, "r", encoding="utf-8") as f: # 修复1:使用 with
content = f.read()
except FileNotFoundError:
print(f"配置文件 '{filename}' 不存在,使用空配置。")
return {}
config = {}
for line_num, line in enumerate(content.split("n"), 1):
line = line.strip()
if not line or line.startswith("#"): # 跳过空行和注释行
continue
if "=" not in line: # 修复2:检查是否含 =
print(f"[警告] 第 {line_num} 行格式不正确,已跳过:'{line}'")
continue
key, value = line.split("=", 1) # maxsplit=1,允许 value 里含 =
config[key.strip()] = value.strip()
return config
# 创建测试配置文件
with open("config.txt", "w", encoding="utf-8") as f:
f.write("# 这是注释n")
f.write("name = Python学习n")
f.write("version = 1.0n")
f.write("这行没有等号n") # 格式错误的行
f.write("debug = truen")
result = load_config("config.txt")
print(result)
输出:
[警告] 第 4 行格式不正确,已跳过:'这行没有等号'
{'name': 'Python学习', 'version': '1.0', 'debug': 'true'}
9. 文件统计工具
题目: 编写函数 analyze_file(filename),统计一个文本文件的:
- 总行数(包括空行)
- 非空行数
- 总字符数(不含换行符)
- 最长的一行的长度和内容
用异常处理文件不存在的情况。
参考答案:
def analyze_file(filename):
try:
with open(filename, "r", encoding="utf-8") as f:
lines = f.readlines()
except FileNotFoundError:
print(f"文件 '{filename}' 不存在")
return
total_lines = len(lines)
non_empty = [l.rstrip("n") for l in lines if l.strip()]
non_empty_count = len(non_empty)
total_chars = sum(len(l.rstrip("n")) for l in lines)
if non_empty:
longest_line = max(non_empty, key=len)
longest_length = len(longest_line)
else:
longest_line, longest_length = "", 0
print(f"=== 文件分析:{filename} ===")
print(f"总行数: {total_lines}")
print(f"非空行数: {non_empty_count}")
print(f"总字符数: {total_chars}(不含换行符)")
print(f"最长行({longest_length} 字符):{longest_line}")
# 创建测试文件
with open("sample.txt", "w", encoding="utf-8") as f:
f.write("Python 是世界上最好的编程语言之一。n")
f.write("n")
f.write("它简洁、易读、功能强大。n")
f.write("适合新手入门,也适合专业开发。n")
f.write("n")
f.write("坚持学习,你一定能掌握 Python!n")
analyze_file("sample.txt")
analyze_file("nonexistent.txt")
输出:
=== 文件分析:sample.txt ===
总行数: 6
非空行数: 4
总字符数: 75(不含换行符)
最长行(18 字符):Python 是世界上最好的编程语言之一。
文件 'nonexistent.txt' 不存在
10. 用户输入验证(异常 + 循环)
题目: 编写程序,让用户输入一个 1~100 之间的整数:
- 如果输入的不是数字(如 “abc”),捕获
ValueError并提示重新输入 - 如果输入的是数字但不在 1~100 之间,提示重新输入
- 输入合法后,打印”输入有效:xx”并结束
参考答案:
def get_valid_number():
"""获取用户输入的 1~100 之间的整数,直到合法为止"""
while True:
raw = input("请输入 1~100 之间的整数:")
try:
number = int(raw)
except ValueError:
print(f" '{raw}' 不是整数,请重新输入。")
continue # 回到循环顶部,重新输入
if not (1 <= number <= 100):
print(f" {number} 不在 1~100 范围内,请重新输入。")
continue
# 到这里说明输入合法
print(f"输入有效:{number}")
return number
result = get_valid_number()
print(f"最终结果:{result}")
交互示例:
请输入 1~100 之间的整数:abc
'abc' 不是整数,请重新输入。
请输入 1~100 之间的整数:150
150 不在 1~100 范围内,请重新输入。
请输入 1~100 之间的整数:-5
-5 不在 1~100 范围内,请重新输入。
请输入 1~100 之间的整数:42
输入有效:42
最终结果:42
关键点: try/except + while True + continue 是处理用户输入校验的经典组合。
11. 文件备份工具
题目: 编写函数 backup_file(filename):
- 读取原文件内容
- 将内容写入备份文件(文件名 = 原文件名 +
.bak,如data.txt.bak) - 打印备份成功信息(包括原文件大小)
- 文件不存在时打印友好提示
- 备份文件已存在时,提示”将覆盖已有备份”
参考答案:
import os
def backup_file(filename):
"""备份文件到 filename.bak"""
backup_name = filename + ".bak"
# 读取原文件
try:
with open(filename, "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
print(f"[失败] 原文件 '{filename}' 不存在,无法备份。")
return False
# 检查备份文件是否已存在
if os.path.exists(backup_name):
print(f"[提示] 备份文件 '{backup_name}' 已存在,将覆盖。")
# 写入备份文件
try:
with open(backup_name, "w", encoding="utf-8") as f:
f.write(content)
except PermissionError:
print(f"[失败] 没有权限写入备份文件 '{backup_name}'。")
return False
size = os.path.getsize(filename)
print(f"[成功] '{filename}'({size} 字节)已备份为 '{backup_name}'")
return True
# 测试
with open("important.txt", "w", encoding="utf-8") as f:
f.write("这是重要数据!n不能丢失!n")
backup_file("important.txt") # 第一次备份
backup_file("important.txt") # 第二次备份(覆盖提示)
backup_file("nonexistent.txt") # 文件不存在
输出:
[成功] 'important.txt'(26 字节)已备份为 'important.txt.bak'
[提示] 备份文件 'important.txt.bak' 已存在,将覆盖。
[成功] 'important.txt'(26 字节)已备份为 'important.txt.bak'
[失败] 原文件 'nonexistent.txt' 不存在,无法备份。
12. 自定义异常继承体系
题目: 为一个学生成绩管理系统设计异常体系:
GradeSystemError(基类):所有业务异常的父类ScoreRangeError(子类):分数超出范围(0~100)StudentNotFoundError(子类):学生不存在