Python编程基础(九) | 文件和异常
引言:很久没有写 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 Pathpath = 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 Pathname = 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 Pathpath = 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 + num2print(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':breakb = input("请输入第二个数字 (或输入'q'退出): ")if b.lower() == 'q':breakresult = 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
语句: 用于立即退出当前的while
或for
循环。在这个例子中,它被用于成功计算后或用户主动选择退出时结束程序。
练习8:猫和狗
创建
cats.txt
和dogs.txt
两个文件。编写一个程序,尝试读取并打印这两个文件的内容,使用try-except
块处理FileNotFoundError
异常。
from pathlib import Pathdef 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 Pathdef read_animal_names_silently(path: Path) -> None:"""尝试读取文件,如果文件不存在则忽略。"""try:contents = path.read_text()print(f"--- 内容来自 {path.name} ---")print(contents)except FileNotFoundError:# 文件不存在时,什么也不做passread_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 Pathdef 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_countpath = 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 jsonpath = 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 jsondef get_stored_user(path: Path) -> dict | None:"""如果存在,则加载存储的用户信息。"""if path.exists():try:user_data = json.loads(path.read_text())return user_dataexcept json.JSONDecodeError:return None # 文件损坏或为空return Nonedef get_new_user(path: Path) -> dict:"""获取新的用户信息并保存。"""user = {}user['name'] = input("你的名字是? ")user['gender'] = input("你的性别是? ")user['age'] = input("你的年龄是? ")path.write_text(json.dumps(user))return userdef 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
) 极大地提高了代码的可读性、可维护性和重用性。 - 用户验证: 在加载持久化数据后增加一个验证步骤,是提高应用程序健壮性和用户体验的重要实践。它确保了程序不会错误地将前一个用户的数据应用到当前用户身上。