第5章 5.3 文件读写与 with 上下文

🎯 开场 3 分钟:为什么要学这个?

上一章我们学会了用正则表达式从一堆乱码里「挖」出手机号、邮箱这些有价值的信息。现在问题来了——挖出来的数据存哪儿?

你肯定遇到过这种情况:写了一个爬虫程序,关掉电脑再打开,数据没了;或者手动复制粘贴结果到 txt 里,结果手抖复制错了行。这种「数据用完就消失」的感觉,就像做饭时没有保鲜盒,食材再好也存不住。

本章我们要解决的就是这个问题:怎么把数据永久保存到文件里,下次想用时还能读出来。学完这章,你就能做出一个真正的「数据存取小工具」,爬虫爬到的数据、清洗完的结果,统统能存进文件里,下次直接拿来用。


🧱 基础 25 分钟:核心概念

什么是文件?用快递盒理解

想象你有一个快递盒
- 打开快递盒 = 读文件(open + read)
- 往盒子里放东西 = 写文件(open + write)
- 封箱 = 关闭文件(close)

普通快递盒有个问题:如果快递员(程序)突然被车撞了(崩溃),盒子里刚放了一半的东西就全乱了。文件也是这个道理——如果你写了一半程序突然崩掉,文件可能就损坏了。

with 语句就像给快递盒装了个自动封箱器:不管里面发生了什么(正常完成 or 突然报错),离开 with 块的时候文件都会被自动关好。数据不会丢,文件不会坏。

配图1 - 配图1

第一个文件操作:读文件

# 打开一个文件,读取内容
with open('notes.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)

这 4 行代码在干嘛:

  1. open('notes.txt', 'r', encoding='utf-8') —— 找到名为 notes.txt 的快递盒,用「读」模式打开(r = read)
  2. as f —— 给这个快递盒起个代号 f,方便后面使唤
  3. f.read() —— 读取盒子里所有内容
  4. 出了 with 块 —— 盒子自动封好,不用你操心

注意! encoding='utf-8' 就像快递盒上的「易碎品」标签,告诉系统用国际通用的编码方式来读中文。没有这个,中文可能会变成乱码。

写文件:创建你的第一个笔记

# 打开一个文件,写入内容
with open('diary.txt', 'w', encoding='utf-8') as f:
f.write('今天学会了文件读写!\n')
f.write('明天继续加油!\n')

解释:
- 'w' 模式 = write,会清空文件重新写(如果文件不存在,会自动创建)
- \n 是换行符,让两句话分两行显示
- 运行完后,当前文件夹会多出一个 diary.txt 文件

追加模式:不覆盖的写入

# 追加模式写入,不会删除原有内容
with open('diary.txt', 'a', encoding='utf-8') as f:
f.write('后天学正则表达式!\n')
  • 'a' 模式 = append,在文件末尾追加新内容
  • 之前写的两句话还在,又多了一行

配图2 - 配图2

pathlib:让路径操作更优雅

传统文件路径长这样:

# Windows 系统常见写法
file_path = 'C:\\Users\\apple\\Desktop\\notes.txt'

# 或者用反斜杠转义
file_path = 'C:/Users/apple/Desktop/notes.txt'

反斜杠容易引发「转义字符」问题(比如 \n 会被当成换行)。用 pathlib 就像用导航软件,路径问题交给它处理:

from pathlib import Path

# 创建一个「路径对象」,自动适配你的系统
desktop = Path.home() / 'Desktop'
notes_file = desktop / 'notes.txt'

# 读写操作一样用 with
with open(notes_file, 'r', encoding='utf-8') as f:
content = f.read()

说白了Path.home() / 'Desktop' 相当于「导航到桌面」,/ 'notes.txt' 相当于「找到 notes.txt」。不管你用的是 Mac 还是 Windows,Python 都会自动帮你拼对路径。

逐行读取:大文件的正确姿势

如果文件有几万行,一次性 read() 可能会把内存撑爆。正确做法是逐行读取

with open('big_data.csv', 'r', encoding='utf-8') as f:
for line in f:  # 像吃面条一样,一口一口来
    print(line.strip())  # strip() 去掉每行末尾的换行符

🔥 实战 35 分钟:3 个递进的小项目

项目 1(5 分钟):自动保存学习笔记

场景:你每天学 Python,想把笔记自动存到文件里。

from datetime import datetime

# 获取今天的日期作为文件名
today = datetime.now().strftime('%Y-%m-%d')
filename = f'笔记_{today}.txt'

# 写入今天的笔记
note_content = """学习进度:
- 学会了正则表达式匹配手机号
- 理解了捕获组的概念

明日计划:
- 继续学习文件读写
"""

with open(filename, 'w', encoding='utf-8') as f:
f.write(note_content)

print(f'笔记已保存到 {filename}')

预期输出:

笔记已保存到 笔记_2026-06-26.txt

解释:这就像给每天的笔记装了一个自动存档功能,文件名自动带上日期,找起来方便。


项目 2(15 分钟):读取 CSV 文件并统计

场景:你有一个 CSV 文件,记录了每天的学习时间,现在要统计这周总共学了多久。

假设 study_log.csv 内容如下:

日期,学习时长(分钟)
2026-06-20,45
2026-06-21,60
2026-06-22,30
2026-06-23,90
2026-06-24,25
2026-06-25,50

代码:

from pathlib import Path

# 读取 CSV 文件
csv_path = Path('study_log.csv')
total_minutes = 0
days_count = 0

with open(csv_path, 'r', encoding='utf-8') as f:
# 跳过第一行(表头)
next(f)

# 逐行读取数据
for line in f:
    line = line.strip()  # 去掉换行符
    if not line:  # 跳过空行
        continue

    # 用 split 按逗号分割
    date, minutes = line.split(',')
    total_minutes += int(minutes)
    days_count += 1

# 计算小时和分钟
hours = total_minutes // 60
minutes = total_minutes % 60

print(f'本周学习 {days_count} 天')
print(f'总计 {hours} 小时 {minutes} 分钟')

预期输出:

本周学习 6 天
总计 5 小时 0 分钟

解释:这就像一个学习打卡计算器,把零散的数据读进来,汇总成一个有意义的统计结果。


项目 3(15 分钟):做个待办清单小工具

场景:做一个命令行待办清单,能添加任务、查看任务、标记完成。所有数据存到文件里,关闭再打开任务还在。

完整代码:

from pathlib import Path

TODO_FILE = Path('todo_list.txt')

def load_tasks():
"""加载任务列表"""
if not TODO_FILE.exists():
    return []
with open(TODO_FILE, 'r', encoding='utf-8') as f:
    return [line.strip() for line in f if line.strip()]

def save_tasks(tasks):
"""保存任务列表"""
with open(TODO_FILE, 'w', encoding='utf-8') as f:
    for task in tasks:
        f.write(task + '\n')

def show_tasks(tasks):
"""显示所有任务"""
if not tasks:
    print('📝 清单是空的,添加一个任务吧!')
    return
print('📋 当前待办清单:')
for i, task in enumerate(tasks, 1):
    print(f'  {i}. {task}')

def add_task(tasks, new_task):
"""添加新任务"""
if not new_task.strip():
    print('❌ 任务内容不能为空!')
    return False
tasks.append(f'[ ] {new_task}')
save_tasks(tasks)
print(f'✅ 已添加:{new_task}')
return True

def complete_task(tasks, task_number):
"""标记任务完成"""
if task_number < 1 or task_number > len(tasks):
    print('❌ 无效的任务编号!')
    return False
task = tasks[task_number - 1]
if task.startswith('[✓]'):
    print('⚠️ 这个任务已经完成了!')
    return False
tasks[task_number - 1] = task.replace('[ ]', '[✓]')
save_tasks(tasks)
print(f'🎉 已完成:{task[4:]}')
return True

def main():
tasks = load_tasks()

print('=' * 30)
print('  📝 待办清单小工具')
print('=' * 30)

while True:
    print('\n请选择操作:')
    print('  1. 查看清单')
    print('  2. 添加任务')
    print('  3. 标记完成')
    print('  4. 退出')

    choice = input('\n你的选择(1/2/3/4):').strip()

    if choice == '1':
        show_tasks(tasks)
    elif choice == '2':
        new_task = input('输入新任务:')
        add_task(tasks, new_task)
    elif choice == '3':
        show_tasks(tasks)
        if tasks:
            num = input('输入要完成的任务编号:')
            if num.isdigit():
                complete_task(tasks, int(num))
    elif choice == '4':
        print('👋 下次见!')
        break
    else:
        print('❌ 无效选择,请重新输入')

if __name__ == '__main__':
main()

预期输出(交互示例):

==============================
📝 待办清单小工具
==============================

请选择操作:
1. 查看清单
2. 添加任务
3. 标记完成
4. 退出

你的选择(1/2/3/4):2
输入新任务:学习文件读写
✅ 已添加:学习文件读写

你的选择(1/2/3/4):1
📋 当前待办清单:
1. [ ] 学习文件读写

解释:这个小工具把「数据持久化」的概念用得很彻底——所有任务都存在文件里,关掉程序再打开,数据还在。下次学新东西时,直接运行就能继续用。


💪 进阶 20 分钟:常见坑 + 性能小贴士

坑 1:忘记指定编码,中文变成乱码

# ❌ 错误写法(Windows 系统默认 GBK 编码,读 UTF-8 会乱码)
with open('notes.txt', 'r') as f:
content = f.read()

# ✅ 正确写法
with open('notes.txt', 'r', encoding='utf-8') as f:
content = f.read()

坑 2:文件不存在还硬要读

# ❌ 错误写法(文件不存在会报错 FileNotFoundError)
with open('maybe_exists.txt', 'r') as f:
content = f.read()

# ✅ 正确写法(先检查文件是否存在)
from pathlib import Path
file_path = Path('maybe_exists.txt')
if file_path.exists():
with open(file_path, 'r', encoding='utf-8') as f:
    content = f.read()
else:
print('文件不存在')

坑 3:写了模式用成了读模式

# ❌ 错误写法(用 'w' 模式打开后还想读取)
with open('notes.txt', 'w', encoding='utf-8') as f:
f.write('hello')
content = f.read()  # 报错:not readable

# ✅ 正确写法(需要读写就用 'r+' 或分开两次 open)
with open('notes.txt', 'r+', encoding='utf-8') as f:
content = f.read()
f.write('\n追加的内容')

坑 4:换行符在不同系统不兼容

# ❌ Windows 换行是 \r\n,Linux/Mac 是 \n
# 手写换行可能在不同系统出问题
content = '第一行\r\n第二行'  # 如果在 Mac 上可能显示异常

# ✅ 正确写法:用 \n,Python 会自动处理
content = '第一行\n第二行'

坑 5:with 块内抛异常,文件没关闭

# ❌ 没用 with 的危险写法
f = open('notes.txt', 'w', encoding='utf-8')
f.write('hello')
raise ValueError('出错了!')  # 程序在这里崩了
f.close()  # 这行永远不会执行,文件没关好

# ✅ 用 with,异常也会自动关闭
with open('notes.txt', 'w', encoding='utf-8') as f:
f.write('hello')
raise ValueError('出错了!')  # 没关系,with 会自动关文件

调试技巧:print 大法好

# 读取前先看看文件存不存在
from pathlib import Path
csv_path = Path('study_log.csv')
print(f'文件存在吗?{csv_path.exists()}')
print(f'绝对路径:{csv_path.absolute()}')

# 读到了?打印前几行看看格式对不对
with open(csv_path, 'r', encoding='utf-8') as f:
for i, line in enumerate(f):
    if i >= 5:  # 只看前 5 行
        break
    print(f'第{i}行:{repr(line)}')  # repr() 显示原始字符

✏️ 练习题

练习 1(2 分钟):抄改文件名

  • 输入:把项目 1 的文件名从 笔记_2026-06-26.txt 改成 我的笔记.txt
  • 预期输出:运行后生成 我的笔记.txt
  • 提示:filename 变量改成字符串字面量就行

练习 2(2 分钟):加个完成判断

  • 输入:在项目 2 的代码里,加一个判断,如果当天学习时间超过 60 分钟就打印「今天很努力!」
  • 预期输出:6 月 23 日(90 分钟)会额外打印「今天很努力!」
  • 提示:在 total_minutes += int(minutes) 后面加个 if int(minutes) > 60: print(...)

练习 3(3 分钟):读取新 CSV

  • 输入:新建一个 expense.csv,内容是「日期,消费金额」,统计总消费
  • 预期输出:打印「本周总消费:XXX 元」
  • 提示:把项目 2 的代码复制过来,改两个变量名就行

练习 4(5 分钟):串个项目 2 和项目 3

  • 输入:让项目 3 支持把任务导出到 CSV 文件
  • 预期输出:选择「导出」时生成 tasks_export.csv
  • 提示:参考项目 2 的 CSV 写入方式

练习 5(3 分钟):看图找 bug

  • 输入:以下代码运行后报 UnicodeDecodeError,找出原因
with open('chinese.txt', 'r') as f:
print(f.read())
  • 预期输出:修复后能正常打印文件内容
  • 提示:检查 encoding 参数

作业:做个「每日一句」名言收藏工具

需求描述:做一个命令行小工具,收集你喜欢的名言佳句,数据永久保存在文件里。

功能点
1. 添加名言(格式:内容 + 作者)
2. 随机展示一条收藏的名言
3. 查看所有收藏

加分项
1. 支持删除某条名言
2. 启动时随机展示一条

验收标准
- 能跑起来
- 添加后关闭再打开,数据还在
- 代码有中文注释

提交方式:评论区贴代码或 GitHub 链接


📚 总结 + 资源

本文学了 3 个核心点:
1. open() + with = 安全读写文件不怕崩
2. encoding='utf-8' = 解决中文乱码问题
3. pathlib.Path = 让路径操作更优雅

延伸学习资源:
- Python 官方文档:文件操作 —— 权威、全面、免费
- 《Python编程:从入门到实践》第 8 章 —— 项目驱动,干货满满
- 视频:B 站「小甲鱼 Python 教程」文件操作章节 —— 讲解细致,适合小白


互动钩子:你在工作/学习中有什么数据需要定期保存的?是笔记、账目、还是其他什么?评论区聊聊实现思路,老粉优先回复!


下一章我们要解决一个新问题:文件里的数据格式千变万化,有 CSV、有 JSON、还有 YAML……学会了这些格式,读写数据才能真正做到「收放自如」。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。