20.Python中的文件和异常练习题

Python 文件和异常练习题(含答案)

1. 创建并读取文件

题目: 编写程序完成以下操作:

  1. 将下面的内容写入文件 greeting.txt
  2. 重新读取文件,打印全部内容
  3. 再次读取文件,打印总行数
你好,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. 追加写入文件

题目: 模拟一个简单的日志系统:

  1. 第一次运行时,写入”日志系统启动”
  2. 之后三次调用 add_log(message) 函数,每次追加一条带序号的日志
  3. 最终读取并打印所有日志

参考答案:

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. 从文件中读取一行,格式为 "数字1 / 数字2"(如 "10 / 2"
  2. 解析并计算除法结果
  3. 使用 try/except/else/finally 完整结构处理所有可能的异常
  4. 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 ValueError
  • age:必须在 1~120 之间,否则 raise ValueError
  • email:必须包含 @,否则 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)

  1. 读取原文件内容
  2. 将内容写入备份文件(文件名 = 原文件名 + .bak,如 data.txt.bak
  3. 打印备份成功信息(包括原文件大小)
  4. 文件不存在时打印友好提示
  5. 备份文件已存在时,提示”将覆盖已有备份”

参考答案:

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(子类):学生不存在

发表评论