先做个实验:昨天你用 random 生成过成绩、用 datetime 记录过时间,现在把程序关掉再打开——那些数据还在吗?不在。因为数据存在内存里,程序一结束,内存就被清空,跟白板写完一擦一样。
想让数据"存得住",就要写进文件:文件存在磁盘上,程序关了它还在,下次打开程序再读出来。就像脑子记不住所有事,所以要写在笔记本上;程序记不住数据,所以要写在文件里。今天学的就是"程序怎么写笔记本、怎么翻笔记本":
open打开文件、write写进去、read读出来、with用完好心把文件关上。另外还有一个中国程序员专属的坑要先打预防针:处理中文必须写encoding="utf-8",不然全是乱码。周五的学生管理系统、第 6 周的大项目,核心都是"文件存取",今天就是打地基。
为什么需要文件:内存 vs 磁盘
先分清两个概念:
- 内存:程序运行时数据住的地方。速度快,但一关程序就清空。
- 磁盘 / 文件:数据长期保存的地方。慢一点,但程序关了数据还在。
所以标准流程是:程序里处理数据(内存)→ 需要长期保存时写进文件(磁盘)→ 下次运行程序时从文件读回内存。这叫数据持久化——说白了,就是让数据活过程序关闭的那一刻。
看一个对比演示:
# 情景 1:不存文件——程序关了就没了
data = ["你好", "Python"]
print("内存里的数据:", data)
print("(现在关掉程序,data 就没了)")
# 情景 2:写进文件——程序关了还在
with open("demo.txt", "w", encoding="utf-8") as f:
f.write("你好,Python\n")
print("已经写进 demo.txt,关程序数据也还在")
输出:
内存里的数据: ['你好', 'Python']
(现在关掉程序,data 就没了)
已经写进 demo.txt,关程序数据也还在
跑完程序,去项目文件夹里找到 demo.txt,双击打开,能看到一行字:你好,Python。这就是"数据活下来了"。
三个容易绕进去的点:
- 别把变量当保存:变量只在程序运行期间有效,关程序就丢;要保存就必须写文件(或数据库,那是后面的事)。
- 文件是给"下次运行"用的:写文件之后,下一次运行程序时读出来,两次运行之间靠文件"接力"。
- 文件位置别搞混:不写路径时,文件创建在程序所在文件夹(工作目录)里。文件找不到,先看这个文件夹。
open 与三种模式:打开文件的三把钥匙
操作文件前要先"打开",就像开门:open(文件名, 模式) 返回一个文件对象,之后所有操作都通过它(用变量 f 接住)。
模式有三把钥匙:
| 模式 | 英文含义 | 做什么 | 文件不存在时 |
|---|---|---|---|
"r" |
read | 读取 | 报错 FileNotFoundError |
"w" |
write | 覆盖写入(会清空原内容) | 自动创建 |
"a" |
append | 追加写入(接着末尾写) | 自动创建 |
另外还有两个组合模式:"rb" / "wb" 二进制模式,处理图片这类文件用,本周不展开。
把三种模式串起来跑一遍:
# w:覆盖写(文件原来有内容也会被清空重写)
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("第一行内容\n")
# a:追加写(在原有内容后面接着写,不清空)
with open("notes.txt", "a", encoding="utf-8") as f:
f.write("追加的第二行内容\n")
# r:读取
with open("notes.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
输出:
第一行内容
追加的第二行内容
注意 w 的破坏性:同一个文件先 w 后 a,w 会把它清空再写,a 不会清空。对应地有三个坑:
w会清空文件——这是新手最大的坑:想"接着写"却用了w,旧数据全没了。追加用a,只有想"推倒重来"才用w。- 读不存在的文件用
r会崩溃:报FileNotFoundError。要先确认文件生成过(下周四学的 try/except 就是兜这个的)。 - 写完不关可能丢数据:写入是"先到内存缓冲,再落盘",不及时关闭,最后那点数据可能没真正写进磁盘。
with 语句:用完好心关门
老式写法是 f = open(...),用完必须手动 f.close() 关文件。一旦忘关,轻则占用资源,重则写入的数据没真正落盘。with open(...) as f: 会在代码块结束后自动关闭文件——不管中间有没有报错,都替你关门。这是 Python 的推荐写法,不用纠结原理,记住这个固定句式就行。
对比一下老式和新式:
# 老式写法(不推荐:要记得手动 close,忘了就出问题)
f = open("notes.txt", "w", encoding="utf-8")
f.write("用老式写法写入")
f.close() # 必须手动关
# 新式写法(推荐:with 结束自动关闭)
with open("notes.txt", "a", encoding="utf-8") as f:
f.write("用 with 写入\n")
# 走到这里文件已经自动关好了,不需要写 close
运行这段没有输出,文件内容变成了两行。
还有个常见坑要演示——在 with 块外使用文件对象:
with open("notes.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content) # OK:内容已经读到变量里了
# 但如果在 with 外面再调 f.read() 会报错(文件已关闭)
# print(f.read()) # ValueError: I/O operation on closed file.
输出:
用老式写法写入用 with 写入
with 的三个讲究:
as f不能省:with open(...):后面必须接as 变量名,否则拿不到文件对象。- with 外面别再用 f:文件已自动关闭,再读写报
ValueError: I/O operation on closed file。要把内容先读到变量里,出了 with 再用。 - 读写操作要缩进在 with 里面:别把
with只当"打开"用,写入、读取这些操作都放在代码块里,缩进不能丢。
encoding="utf-8":中文不乱码的保命符
电脑存文字靠编码——文字和数字的对应表。Python 默认用系统编码,在 Windows 上处理中文文件容易乱码甚至直接报错。解决办法就一条:打开文件时永远带上 encoding="utf-8"。utf-8 是目前最通用的编码,几乎所有地方都认。
- 写文件不带:中文可能被存成乱码。
- 读文件不带:可能报
UnicodeDecodeError(解码失败)或读到乱码。
读、写、追加,全都写 encoding="utf-8",别偷懒。看正确姿势:
# 正确姿势:读写都带 encoding="utf-8"
with open("study.txt", "w", encoding="utf-8") as f:
f.write("今天学习了文件读写\n")
f.write("中文一定记得 utf-8\n")
with open("study.txt", "r", encoding="utf-8") as f:
print(f.read())
输出:
今天学习了文件读写
中文一定记得 utf-8
(如果不带 encoding 去读刚才的文件,在 Windows 上可能报 UnicodeDecodeError: 'gbk' codec can't decode byte ...。)
编码这件事,三个地方要注意:
- 三处编码要统一:写的时候、读的时候、以及你用记事本打开看的时候,编码不一致就乱码。
- utf-8 不是唯一编码:网上有些老文件是 gbk 的,读的时候报错就把
"utf-8"换成"gbk"试试(了解即可)。 - 参数拼写是
encoding不是encode:写错报TypeError: 'encoding' is an invalid keyword argument。
读取的三种方式:整读、逐行读
读文件有几种方式,按需选:
f.read():一次性把整个文件读成一个大字符串。f.readline():一次读一行(不常用)。for line in f::一行一行遍历(最常用),文件大也不会卡死。f.readlines():读成"每行一个元素的列表"。
有个细节:读出来的每行末尾都带着换行符 \n,直接打印会多一个空行,用 .strip() 去掉首尾空白(包括换行符)。
# 先准备一个文件
with open("names.txt", "w", encoding="utf-8") as f:
f.write("小明\n小红\n小刚\n")
# 方式 1:整篇读成一个大字符串
with open("names.txt", "r", encoding="utf-8") as f:
content = f.read()
print("read() 输出:", repr(content)) # repr 能看到换行符长什么样
# 方式 2(推荐):一行一行处理
with open("names.txt", "r", encoding="utf-8") as f:
for line in f:
print("读到:", line.strip()) # strip 去掉行尾的 \n
输出:
read() 输出: '小明\n小红\n小刚\n'
读到: 小明
读到: 小红
读到: 小刚
再看一眼 strip() 到底干了什么:
line = "小明\n"
print(repr(line)) # 有 \n
print(repr(line.strip())) # 去掉后
输出:
'小明\n'
'小明'
三个读取上的坑:
- 每行末尾有
\n:直接print(line)会多一个空行,处理时先.strip()。 read()读一次就"读完"了:文件对象有"读指针",第二次f.read()拿到空字符串,想重读要重新 open。- 小文件循环读用
for line in f:比f.readlines()更省内存、写法最简单,记住这个就够用。
随堂练习
-
练习一(三步走,必做):把 3 行"今日学习内容"写进
study.txt,再追加 1 行,最后读出来打印。 提示:先用w写 3 行(记得每行末尾\n),再用a追加 1 行,最后用r+for line in f逐行打印。 -
练习二(文件统计):读一个文本文件,统计它有多少行、多少个字,并打印。 提示:行数用
for line in f循环计数(每读一行行数 += 1);字数可以先read()再len(content) - content.count(" ")(减掉空格),或者content.split()拆成词列表再数。先用记事本建一个article.txt放几行文字再测。 -
练习三(升级猜数字,进阶):把昨天写的"猜数字"升级:每玩完一局,把"你猜了 N 次"的成绩追加写进
record.txt,每次运行都追加一行,最后读出来看看历史成绩。 提示:猜的次数是变量,写文件时用 f-string 拼成一行字符串;用a模式追加,别用w(会把历史成绩清空)。 -
★ 进阶题(多行记账小文件):让用户循环输入"今天干了什么",输入
q结束,把输入的内容逐行写进diary.txt(追加模式),退出后读取并打印全部内容。 提示:while True+break;每次输入后立即写入并刷新;最后再 open 读取。
报错了?让 AI 帮你查
文件报错种类多,自己排查慢,这三段提示词直接复制——
场景:文件读写报错 / 不知道怎么写
提示词:
我是 Python 初学者。想把一个列表里的 3 句话写进一个 txt 文件,
然后追加 1 句,最后读出来逐行打印。请给我代码,要求:
用 with open 写法,所有打开文件的地方都带 encoding="utf-8",
加中文注释,每行一个要点。
场景:统计文件内容
提示词:
请帮我写一个 Python 程序:读取一个 txt 文件,统计它有多少行、
多少个字(不含空格),然后打印结果。用 with open 写法,
带 encoding="utf-8",加注释,代码尽量简单,我是初学者。
场景:理解三种模式
提示词:
用大白话给我讲讲 open 文件时 w、a、r 三种模式的区别,
尤其是我什么时候该用 a 而不是 w。给我一个"会清空文件"的
反例演示。我是零基础,别讲太深。
常见报错速查
| 报错信息 | 什么意思 | 怎么办 |
|---|---|---|
FileNotFoundError: [Errno 2] No such file or directory |
要读的文件不存在 | 检查文件名和路径拼写;用 w 模式先创建,再读 |
UnicodeDecodeError: 'gbk' codec can't decode byte ... |
编码不匹配,读中文乱码 | 打开文件时加 encoding="utf-8" |
UnicodeEncodeError: 'gbk' codec can't encode ... |
写入中文时没指定编码 | 写入时也加 encoding="utf-8" |
ValueError: I/O operation on closed file |
在 with 块外面使用文件对象 | 把读写操作放进 with 块里,或先把内容存到变量 |
PermissionError: [Errno 13] |
文件被别的程序(如记事本)占用 | 关掉占用文件的其他程序再运行 |
IsADirectoryError: [Errno 21] |
把文件夹当成文件打开了 | 确认打开的是文件路径,不是文件夹路径 |
收个尾
今天的内容可以浓缩成一句固定句式,以后写文件代码直接套:
with open(文件名, 模式, encoding="utf-8") as f:——w 覆盖、a 追加、r 读取,w 会清空文件要小心;encoding="utf-8" 保中文不乱码;读文件优先 for line in f 逐行处理,每行记得 .strip()。
明天学异常处理:程序"摔倒了能自己爬起来"。今天读文件报 FileNotFoundError 时程序直接崩溃——明天就能用 try/except 让它友好地提示"文件不存在"而不是当场死掉。你能想到,除了读文件,还有哪些地方随时可能"摔倒"吗?