Python编程基础(九) | 文件和异常

发布于:2025-09-12 ⋅ 阅读:(21) ⋅ 点赞:(0)

引言:很久没有写 Python 了,有一点生疏。这是学习《Python 编程:从入门到实践(第3版)》的课后练习记录,主要目的是快速回顾基础知识。

练习1: Python学习笔记

在文本编辑器中新建一个文件 learning_python.txt,写几句话总结你学到的Python知识,每行都以“In Python you can”开头。编写一个程序,读取这个文件,并将内容打印两次:第一次读取整个文件,第二次遍历文件对象。

from pathlib import Path

# 假设 learning_python.txt 文件内容如下:
# In Python you can store data in variables.
# In Python you can use loops to iterate over items.
# In Python you can write functions to organize code.

path = Path('learning_python.txt')

# 方法一:读取并打印整个文件
print("--- Reading the entire file at once: ---")
contents = path.read_text()
print(contents)

# 方法二:遍历文件中的每一行
print("\n--- Looping over the file's lines: ---")
lines = contents.splitlines()
for line in lines:
    print(line)
--- Reading the entire file at once: ---
In Python you can store data in variables.
In Python you can use loops to iterate over items.
In Python you can write functions to organize code.

--- Looping over the file's lines: ---
In Python you can store data in variables.
In Python you can use loops to iterate over items.
In Python you can write functions to organize code.

知识点回顾:

  • pathlib 模块: 这是处理文件路径的现代化、面向对象的方式。Path('filename.txt') 创建一个路径对象。
  • 读取文件: path.read_text() 方法一次性读取文件的全部内容,并返回一个字符串。这对于小文件非常方便。
  • 处理行: contents.splitlines() 方法将一个大字符串按行分割,并返回一个包含所有行的列表。
  • 逐行遍历: 使用 for 循环遍历由 splitlines() 生成的列表,是处理文件内容最常见的方式之一。

练习2: C语言学习笔记

读取你创建的 learning_python.txt 文件,将其中的 “Python” 都替换为另一门语言的名称(如 “C”),并将修改后的各行打印到屏幕上。

from pathlib import Path

path = Path('learning_python.txt')
contents = path.read_text()

for line in contents.splitlines():
    # 使用 replace() 方法进行替换
    modified_line = line.replace('Python', 'C')
    print(modified_line)
In C you can store data in variables.
In C you can use loops to iterate over items.
In C you can write functions to organize code.

知识点回顾:

  • 字符串方法 replace(): string.replace('old', 'new') 会返回一个新的字符串,其中所有出现的 'old' 子字符串都被 'new' 替换。原始字符串 line 本身保持不变。
  • 方法应用: 这个练习展示了如何将文件读取与字符串处理方法结合起来,对文件内容进行动态修改和输出。

练习3:简化代码

在前面的程序中,我们通常会用一个临时变量来存储 splitlines() 的结果。实际上,可以直接遍历 contents.splitlines() 返回的列表来让代码更简洁。

from pathlib import Path

# 以练习二为例进行简化
path = Path('learning_python.txt')

# 将 read_text() 和 splitlines() 链式调用
for line in path.read_text().splitlines():
    print(line.replace('Python', 'Rust'))
in Rust you can ...1
in Rust you can ...2
in Rust you can ...3

知识点回顾:

  • 方法链式调用 (Method Chaining):当一个方法返回一个对象时,可以立即在该对象上调用另一个方法,如 path.read_text().splitlines()。这使得代码更紧凑。
  • 代码简洁性:通过省略不必要的临时变量,代码的行数减少,意图也可能更直接。不过,在复杂操作中,使用临时变量有时能提高代码的可读性。

练习4:访客

编写一个程序,提示用户输入名字,然后将其名字写入文件 guest.txt

from pathlib import Path

name = input("请输入你的名字:")
path = Path("guest.txt")
path.write_text(name)

print(f"你好, {name},你的名字已被记录。")
请输入你的名字:孔乙己
你好, 孔乙己,你的名字已被记录。```

**知识点回顾:**
*   **用户输入 `input()`**: `input()` 函数用于从用户那里获取文本输入,它总是返回一个字符串。
*   **写入文件 `write_text()`**: `path.write_text(data)` 会将字符串 `data` 写入到文件中。**注意**:此方法会覆盖文件的所有现有内容。如果文件不存在,它会自动创建。

### 练习5:访客簿
> 编写一个 `while` 循环,提示用户输入名字。收集所有输入的名字,并将它们写入 `guest_book.txt`,确保每条记录都独占一行。

```python
from pathlib import Path

path = Path("guest_book.txt")
guest_list = []

while True:
    name = input("请输入你的名字 (输入 'q' 退出): ")
    if name == 'q':
        break
    
    # 将名字和换行符一起添加到列表中
    guest_list.append(name + '\n')

# 将列表中的所有字符串连接起来并写入文件
path.write_text("".join(guest_list))
print("访客名单已保存。")
请输入你的名字 (输入 'q' 退出): 孔乙己
请输入你的名字 (输入 'q' 退出): 阿Q
请输入你的名字 (输入 'q' 退出): 祥林嫂
请输入你的名字 (输入 'q' 退出): q
访客名单已保存。

知识点回顾:

  • while 循环: 用于重复执行代码块,直到特定条件(用户输入’q’)满足为止。
  • 哨兵值 (Sentinel Value):‘q’ 在这里是一个哨兵值,它的出现标志着循环的结束。
  • 换行符 \n: 在字符串末尾添加 \n,可以确保在写入文件时内容会换行。
  • 构建内容后一次性写入: 先将所有内容收集到一个列表 guest_list 中,然后使用 "".join(guest_list) 将它们合并成一个大字符串,最后一次性写入文件。这通常比在循环中反复打开和写入文件更高效。

练习6:加法运算

编写一个程序,提示用户输入两个数,然后将它们相加。捕获 ValueError 异常,以防用户输入的不是数字。

try:
    num1 = float(input("请输入第一个数:"))
    num2 = float(input("请输入第二个数:"))
except ValueError:
    print("输入错误,请输入有效的数字!")
else:
    result = num1 + num2
    print(f"两数之和为:{result}")
请输入第一个数:10
请输入第二个数:a
输入错误,请输入有效的数字!

知识点回顾:

  • 异常处理 try-except: 这是 Python 中处理错误的强大机制。try 块中的代码是被监控的,如果发生特定类型的错误(如 ValueError),程序会跳转到相应的 except 块执行,而不是直接崩溃。
  • ValueError: 当一个函数(如 float())接收到一个类型正确但值不合适的参数时,会引发此异常。例如,float('a') 就会引发 ValueError
  • else 代码块: try 块中没有发生异常时,else 块中的代码会被执行。这非常适合放置只有在 try 块成功后才应运行的代码。

练习7:加法计算器

将练习6的代码放入一个 while 循环中,让用户可以持续进行计算,直到成功完成一次加法运算。

while True:
    try:
        a = input("请输入第一个数字 (或输入'q'退出): ")
        if a.lower() == 'q':
            break
        
        b = input("请输入第二个数字 (或输入'q'退出): ")
        if b.lower() == 'q':
            break

        result = float(a) + float(b)

    except ValueError:
        print("输入错误,请输入有效的数字!")
    else:
        print(f'数之和为:{result}')
        print("计算成功,程序结束。")
        break
请输入第一个数字 (或输入'q'退出): 1
请输入第二个数字 (或输入'q'退出): q
请输入第一个数字 (或输入'q'退出): 1
请输入第二个数字 (或输入'q'退出): 3
数之和为:4.0
计算成功,程序结束。

知识点回顾:

  • 循环与异常处理的结合: 这是创建健壮的、交互式程序的常用模式。循环确保程序持续运行,而 try-except 块处理每次迭代中可能出现的错误输入,防止程序因单次错误而终止。
  • break 语句: 用于立即退出当前的 whilefor 循环。在这个例子中,它被用于成功计算后或用户主动选择退出时结束程序。

练习8:猫和狗

创建 cats.txtdogs.txt 两个文件。编写一个程序,尝试读取并打印这两个文件的内容,使用 try-except 块处理 FileNotFoundError 异常。

from pathlib import Path

def read_animal_names(path: Path) -> None:
    """尝试读取并打印一个文件,如果文件不存在则打印错误信息。"""
    try:
        contents = path.read_text()
        print(f"--- 内容来自 {path.name} ---")
        print(contents)
    except FileNotFoundError:
        print(f"错误:找不到文件 {path}。")

# 假设 cats.txt 存在,而 dogs.txt 不存在
get_file(Path("cats.txt"))
get_file(Path("dogs.txt"))
--- 内容来自 cats.txt ---
Mimi
Kitty
Garfield

错误:找不到文件 dogs.txt。

知识点回顾:

  • FileNotFoundError: 当尝试打开或操作一个不存在的文件时,Python 会引发这个特定的异常。
  • 优雅地处理错误: 通过捕获 FileNotFoundError,程序可以在某个文件缺失时继续执行,而不是崩溃。这对于处理可选的配置文件或数据文件非常有用。
  • 代码封装: 将文件读取逻辑封装在一个函数 (read_animal_names) 中,使得代码更易于重用和理解。

练习9:静默的猫和狗

修改练习8的 except 块,让程序在文件不存在时静默失败(即什么也不做)。

from pathlib import Path

def read_animal_names_silently(path: Path) -> None:
    """尝试读取文件,如果文件不存在则忽略。"""
    try:
        contents = path.read_text()
        print(f"--- 内容来自 {path.name} ---")
        print(contents)
    except FileNotFoundError:
        # 文件不存在时,什么也不做
        pass

read_animal_names_silently(Path("cats.txt"))
read_animal_names_silently(Path("dogs.txt"))
--- 内容来自 cats.txt ---
Mimi
Kitty
Garfield

知识点回顾:

  • pass 语句: pass 是一个空操作占位符。当语法上需要一个语句,但程序不需要执行任何操作时,就可以使用它。
  • 静默失败 (Silent Failure): 在 except 块中使用 pass 会导致错误被“吞噬”,程序会悄无声息地继续执行。这在某些情况下是期望的行为(例如,加载一个可选的配置文件),但需要谨慎使用,因为它也可能隐藏真正的问题。

练习10:常见单词

编写一个程序,读取一个文本文件,并计算单词 “the” 在其中出现了多少次。

from pathlib import Path

def count_specific_word(path: Path, word: str) -> int:
    """计算一个特定单词在文件中出现的次数(忽略大小写)。"""
    try:
        contents = path.read_text(encoding='utf-8')
    except FileNotFoundError:
        print(f'错误:文件 {path} 不存在。')
        return 0
    
    # 转换为小写以进行不区分大小写的计数
    word_count = contents.lower().count(f" {word} ")
    return word_count

path = Path('moby_dick.txt') # 假设从古登堡计划下载了《白鲸》
the_count = count_specific_word(path, 'the')

# 直接使用 count() 会包含 'then', 'there' 等
inaccurate_count = path.read_text(encoding='utf-8').lower().count('the')

print(f"使用 str.count('the') 的结果 (不准确): {inaccurate_count}")
print(f"计算独立的单词 'the' 的结果 (更准确): {the_count}")
使用 str.count('the') 的结果 (不准确): 14431
计算独立的单词 'the' 的结果 (更准确): 13721

知识点回顾:

  • str.lower(): 将整个字符串转换为小写,这是进行不区分大小写文本分析的第一步。
  • str.count(): 一个简单快捷的方法,用于计算子字符串在字符串中出现的次数。
  • 分析的精确性: 直接使用 count('the') 会错误地计算包含 “the” 的单词(如 “there”, “other”)。通过搜索 f" {word} "(单词两边带空格)是一种更精确的近似方法,尽管它会漏掉句首和句尾的 “the”。一个更完善的解决方案需要使用正则表达式或更复杂的文本分割。

练习11 & 12:记住喜欢的数

编写一个程序,如果用户喜欢的数字已经存储,则读取并显示它。否则,提示用户输入并将其存储在文件中。

from pathlib import Path
import json

path = Path("favorite_number.json")

try:
    # 尝试读取文件
    contents = path.read_text()
    number = json.loads(contents)
except (FileNotFoundError, json.JSONDecodeError):
    # 如果文件不存在或内容不是有效的JSON,则获取新数字
    number = input("你最喜欢的数字是什么? ")
    contents = json.dumps(number)
    path.write_text(contents)
    print("谢谢,我已经记住你的数字了!")
else:
    # 如果读取成功,则显示它
    print(f"我知道你最喜欢的数字!它是 {number}。")
# 第一次运行
你最喜欢的数字是什么? 7
谢谢,我已经记住你的数字了!

# 第二次运行
我知道你最喜欢的数字!它是 7。

知识点回顾:

  • json 模块: Python 的标准库,用于处理 JSON (JavaScript Object Notation) 数据格式。
  • json.dumps() (dump string): 将 Python 对象(如数字、字符串、列表、字典)序列化成 JSON 格式的字符串。
  • json.loads() (load string): 将 JSON 格式的字符串反序列化成 Python 对象。
  • 数据持久化: 使用 json 和文件 I/O,可以让程序“记住”上次运行时的数据,实现了简单的持久化存储。

练习13 & 14:验证用户

扩展 remember_me.py 示例,存储更多用户信息(姓名、性别、年龄),并在程序启动时验证当前用户是否是上一次的用户。

from pathlib import Path
import json

def get_stored_user(path: Path) -> dict | None:
    """如果存在,则加载存储的用户信息。"""
    if path.exists():
        try:
            user_data = json.loads(path.read_text())
            return user_data
        except json.JSONDecodeError:
            return None # 文件损坏或为空
    return None

def get_new_user(path: Path) -> dict:
    """获取新的用户信息并保存。"""
    user = {}
    user['name'] = input("你的名字是? ")
    user['gender'] = input("你的性别是? ")
    user['age'] = input("你的年龄是? ")
    path.write_text(json.dumps(user))
    return user

def greet_user():
    """问候用户,并确认身份。"""
    path = Path('user.json')
    user = get_stored_user(path)
    
    if user:
        # 验证用户
        prompt = f"这是你吗, {user['name']}? (y/n) "
        if input(prompt).lower() == 'y':
            print(f"欢迎回来, {user['name']}!")
            print(f"- 性别: {user['gender']}, 年龄: {user['age']}")
        else:
            # 不是同一个用户,获取新用户信息
            user = get_new_user(path)
            print(f"好的,{user['name']},我们会记住你的信息。")
    else:
        # 没有存储的用户,获取新用户信息
        user = get_new_user(path)
        print(f"好的,{user['name']},我们会记住你的信息。")

greet_user()
# 第一次运行
你的名字是? 张三
你的性别是? 男
你的年龄是? 18
好的,张三,我们会记住你的信息。

# 第二次运行 (由张三本人)
这是你吗, 张三? (y/n) y
欢迎回来, 张三!
- 性别: 男, 年龄: 18

# 第三次运行 (由李四)
这是你吗, 张三? (y/n) n
你的名字是? 李四
你的性别是? 男
你的年龄是? 20
好的,李四,我们会记住你的信息。

知识点回顾:

  • 存储复杂数据: JSON 非常适合存储像字典这样的结构化数据,可以轻松地保存多个相关信息。
  • 代码重构: 将逻辑划分为独立的函数 (get_stored_user, get_new_user, greet_user) 极大地提高了代码的可读性、可维护性和重用性。
  • 用户验证: 在加载持久化数据后增加一个验证步骤,是提高应用程序健壮性和用户体验的重要实践。它确保了程序不会错误地将前一个用户的数据应用到当前用户身上。

网站公告

今日签到

点亮在社区的每一天
去签到