一、为什么要把数据存进文件
前面学的变量、列表,程序一关就没了。想让数据下次开机还在——比如全班的成绩、你写的日记、程序跑出来的日志——就得存进文件。
# 变量存在内存里,程序关掉就消失
scores = [88, 72, 95, 60]
print('本次运行算好了,但关掉就没了')
把数据写进文件,就像把作业写进本子:合上本子,字迹还在。下次打开还能看。
| 存哪 | 关掉程序后还在吗 | 适合什么 |
|---|---|---|
| 变量 / 列表(内存) | 不在 | 本次运行临时用 |
| 文件(硬盘) | 在 | 要长期保存、下次还要用的数据 |
open() 就是打开文件、读写内容的那把钥匙。课程思政|诚信与职业操守 · 数据要有据可查
变量和列表住在内存里,程序一关就烟消云散;写进文件,数据才留得下来、下次还能复盘。做经济统计尤其要有这个意识:留痕不是多事,是让结论站得住脚。原始数据及时落盘、整理过程可以追溯,将来别人质疑你的结果,你能一条条翻出来讲清楚,这份底气就是职业操守给的。
二、open() 打开文件
读写文件的第一步是 open():把「文件名」交给它,它返回一个文件对象,后面读写都靠这个对象。
with open('scores.txt', 'w', encoding='utf-8') as f:
f.write('88\n72\n95\n60\n')
f = open('scores.txt', 'r', encoding='utf-8')
print(type(f))
f.close()
输出 <class '_io.TextIOWrapper'>(不用背这个名字,知道它是「文件对象」就行)。读完记得 close() 关掉。(这里故意用了一次老写法,让你看看文件对象的长相;close() 的讲究第五节马上讲,平时统一用 with 就行。)
三个最常用的参数
| 参数 | 含义 | 例子 |
|---|---|---|
| 第 1 个 | 文件名(或路径) | 'scores.txt' |
| 第 2 个 | 模式:'r' 读 / 'w' 写 / 'a' 追加 | 'r' |
encoding | 编码,中文几乎总是写 'utf-8' | encoding='utf-8' |
三种模式
| 模式 | 作用 | 文件不存在时 | 已存在时 |
|---|---|---|---|
'r' | 只读 | 报错 | 原样读 |
'w' | 写入(会清空) | 自动新建 | 原内容被清空 |
'a' | 追加到末尾 | 自动新建 | 在末尾接着写,不清空 |
encoding='utf-8'。不写的话,Python 就按「系统默认编码」处理:从 Python 3.15.0 起,默认编码已统一为 utf-8;更早的版本(尤其 Windows 上的)默认用 gbk,读你写的 utf-8 文件就可能乱码(详见第十节)。养成习惯:每次 open 都带 encoding。'rb' 是「二进制读」、'w+' 是「读写」。起步阶段先把 'r' / 'w' / 'a' 三个用熟。三、读文件的三种方式
拿到读模式('r')的文件对象后,有三种读法。
1. read():一口气读完整篇
with open('scores.txt', 'w', encoding='utf-8') as f:
f.write('88' + '\n' + '72' + '\n' + '95' + '\n' + '60' + '\n')
with open('scores.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
输出:
88
72
95
60
read() 返回一个字符串,包含所有内容(含换行符)。
2. readlines():按行读成列表
with open('scores.txt', 'w', encoding='utf-8') as f:
f.write('88' + '\n' + '72' + '\n' + '95' + '\n')
with open('scores.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
print(lines)
输出 ['88\n', '72\n', '95\n']。每行是列表里的一个元素,行尾的换行符也带着。
3. 逐行 for:一行一行处理
with open('scores.txt', 'w', encoding='utf-8') as f:
f.write('88' + '\n' + '72' + '\n')
with open('scores.txt', 'r', encoding='utf-8') as f:
for line in f:
print(line.strip()) # strip() 去掉首尾空白和换行
输出:
88
72
read();要数行数或按行取用 readlines();文件很大、一行行处理用 for line in f(最省内存)。四、写文件
写模式('w')用 write() 把字符串写进去。注意:write 只收字符串,不自动换行。
with open('diary.txt', 'w', encoding='utf-8') as f:
f.write('2026-09-21 今天学了 open()' + '\n')
print('写好了')
输出 写好了。文件里多了一行日记('
' 是换行符,让每次写占一行)。
'w' 会清空原内容
同一个文件用 'w' 再开一次,里面的旧东西就没了:
with open('e.txt', 'w', encoding='utf-8') as f:
f.write('旧内容')
with open('e.txt', 'w', encoding='utf-8') as f: # 又一次 w,原内容被清空
f.write('新内容')
with open('e.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出 新内容。第一次写的「旧内容」已经被第二次 'w' 冲掉了。
'a' 是追加,不清空
with open('log.txt', 'a', encoding='utf-8') as f:
f.write('2026-09-21 登录' + '\n')
with open('log.txt', 'a', encoding='utf-8') as f:
f.write('2026-09-21 退出' + '\n')
with open('log.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出:
2026-09-21 登录
2026-09-21 退出
两次都用 'a',内容叠在后面,不会互相覆盖。日志、记录类文件就用它。
'w',用 'a'。一个 'w' 下去,之前写的所有内容瞬间消失,且找不回来。课程思政|数据安全与法治素养 · 按下保存前,先备份
'w' 模式一开,文件里的旧内容瞬间清零,而且找不回来——很多事故不是程序写错,是一个模式参数就抹掉了整份数据。覆盖之前,先留一份备份。真实的统计、财务数据往往不可再生,改之前先另存、删之前先确认,这不是胆小,是数据工作者的基本责任。
五、关文件与 with open
文件用完后要 close() 关掉,否则内容可能没真正写进硬盘(存在缓冲区里)。
老写法:手动 close
f = open('a.txt', 'w', encoding='utf-8')
f.write('hello')
f.close() # 必须记得关
推荐写法:with open(自动关)
with open('a.txt', 'w', encoding='utf-8') as f:
f.write('hello')
# 离开这个缩进块,文件自动关闭,不用写 close()
两种写法效果一样,但 with 会在代码块结束时自动关闭文件,就算中间出错了也会关。新手强烈推荐 with。
| 手动 close | with open | |
|---|---|---|
| 会不会忘关 | 容易忘 | 不会,自动关 |
| 中途报错 | 可能没关上 | 照样关好 |
| 代码量 | 多一行 close | 少一行,更干净 |
with open('文件名', 'r', encoding='utf-8') as f: 内容 = f.read()读和写都把操作写在缩进块里即可。
六、路径怎么写
open() 第一个参数是「文件在哪」。有两种写法:
| 写法 | 含义 | 例子 |
|---|---|---|
| 相对路径 | 相对「你运行代码时的目录」 | 'scores.txt'(当前目录) |
| 绝对路径 | 从根目录写全 | '/tmp/tut_demo/scores.txt' |
相对路径最省事:写 'scores.txt',文件就创建在你运行代码的那个文件夹里。本篇所有例子都用相对路径,方便你照抄。
Windows 上的反斜杠坑
Windows 路径用反斜杠 \,而反斜杠在 Python 字符串里是转义符。所以要写成两个反斜杠或原始字符串 r"":
# 示意:Windows 上的正确写法
open('C:\\Users\\笔记\\data.txt')
open(r'C:\Users\笔记\data.txt')
在 Mac / Linux 上路径用正斜杠 /,不用操心这个。教学示例都在 /tmp/tut_demo/ 下验证,与系统无关。
'C:\Users\name.txt' 只写一个反斜杠会报错或路径错乱——要么写两个 \\,要么在整个字符串前加 r 变成原始字符串。七、文件不存在会怎样
用 'r' 读一个不存在的文件,Python 会直接报错:
with open('不存在.txt', 'r', encoding='utf-8') as f:
print(f.read())
FileNotFoundError: [Errno 2] No such file or directory: '不存在.txt'
原因:要读的文件根本没创建过。先确认文件名、路径拼写对不对。
但 'w' 和 'a' 会帮你自动创建
写模式和追加模式很「友好」:文件不在,它直接帮你建一个。
import os
print(os.path.exists('newf.txt'))
with open('newf.txt', 'w', encoding='utf-8') as f:
f.write('创建我')
print(os.path.exists('newf.txt'))
输出:
False
True
第一次打印 False(还不存在),写完再打印就 True 了。
os.path.exists('文件名')(别忘了先 import os)。读之前先判断,能避免一大半的 FileNotFoundError。AI 助手小贴士|with 的价值要用「不用它会怎样」来衬托。
打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:with open(...) as f 到底帮我关了什么?不用 with 且忘了 close 会出什么问题?给两个对照的小例子
课程思政|工匠精神与严谨求实 · 动手之前,先看一眼
读文件前用 os.path.exists() 问一句「它在不在」,就能躲开一大半 FileNotFoundError。小事一桩,却是好习惯:先确认前提,再动手。不臆断、不硬闯,把「我以为它有」换成「我确认真有」,这份谨慎挪到写论文、做报表上,就是让人放心的严谨。
八、配合异常处理读文件
第七节的报错可以用 try...except 接住,让程序「文件不在就提示,而不是直接崩」:
try:
with open('config.txt', 'r', encoding='utf-8') as f:
print(f.read())
except FileNotFoundError:
print('配置文件不存在,使用默认设置')
把 config.txt 换成不存在的文件运行,输出就是 配置文件不存在,使用默认设置,程序稳稳继续。
| 场景 | 不处理 | 用 try...except 处理 |
|---|---|---|
| 读不存在的文件 | 程序崩溃报错 | 给出友好提示,继续运行 |
| 读用户指定的文件 | 用户手滑就崩 | 提示「文件不存在」让用户重来 |
try...except FileNotFoundError 是好习惯。异常处理的完整讲法见《25-异常处理》,这里先会用这一种。九、十个基础案例
下面十个例子都很短,建议每个都亲手敲一遍、跑一遍。它们覆盖了文件操作最常见的用法。
1. 写一条购物清单
with open('购物清单.txt', 'w', encoding='utf-8') as f:
f.write('牛奶 2 盒' + '\n')
print('清单已保存')
输出 清单已保存。
2. 读购物清单
with open('购物清单.txt', 'w', encoding='utf-8') as f:
f.write('牛奶 2 盒' + '\n' + '面包 1 袋' + '\n' + '鸡蛋 10 个' + '\n' + '酸奶 3 瓶' + '\n')
with open('购物清单.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出:
牛奶 2 盒
面包 1 袋
鸡蛋 10 个
酸奶 3 瓶
3. 数一数清单有几样
with open('购物清单.txt', 'w', encoding='utf-8') as f:
f.write('牛奶 2 盒' + '\n' + '面包 1 袋' + '\n' + '鸡蛋 10 个' + '\n' + '酸奶 3 瓶' + '\n')
with open('购物清单.txt', 'r', encoding='utf-8') as f:
print(len(f.readlines()))
输出 4。
4. 追加一条
with open('追加清单.txt', 'a', encoding='utf-8') as f:
f.write('牛奶 2 盒' + '\n')
with open('追加清单.txt', 'a', encoding='utf-8') as f:
f.write('纸巾 1 提' + '\n')
with open('追加清单.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出:
牛奶 2 盒
纸巾 1 提
5. 复制清单
with open('清单A.txt', 'w', encoding='utf-8') as f:
f.write('奶茶')
with open('清单A.txt', 'r', encoding='utf-8') as s, open('清单B.txt', 'w', encoding='utf-8') as d:
d.write(s.read())
with open('清单B.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出 奶茶。
6. 统计口味出现次数
with open('口味.txt', 'w', encoding='utf-8') as f:
f.write('珍珠 椰果 珍珠 布丁 椰果 珍珠')
from collections import Counter
with open('口味.txt', 'r', encoding='utf-8') as f:
print(Counter(f.read().split()))
输出 Counter({'珍珠': 3, '椰果': 2, '布丁': 1})。
7. 逐行读价格求总价
with open('价格.txt', 'w', encoding='utf-8') as f:
f.write('12' + '\n' + '8' + '\n' + '15' + '\n' + '20' + '\n')
total = 0
with open('价格.txt', 'r', encoding='utf-8') as f:
for line in f:
total += int(line)
print(total)
输出 55。
8. 一次写多行
lines = ['牛奶 2 盒', '面包 1 袋', '鸡蛋 10 个']
with open('多行清单.txt', 'w', encoding='utf-8') as f:
for ln in lines:
f.write(ln + '\n')
with open('多行清单.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出:
牛奶 2 盒
面包 1 袋
鸡蛋 10 个
9. 判断文件是否存在
import os
print(os.path.exists('存在检查.txt'))
with open('存在检查.txt', 'w', encoding='utf-8') as f:
f.write('x')
print(os.path.exists('存在检查.txt'))
输出:
False
True
10. 把总价存成文件
r = sum([12, 8, 15, 20])
with open('总价.txt', 'w', encoding='utf-8') as f:
f.write(str(r))
with open('总价.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出 55。
十、常见坑
坑 1:忘了 close(或没用 with)
不用 with 又忘写 close(),写进去的内容可能卡在缓冲区、没真正落盘。直接养成用 with 的习惯最保险。
坑 2:用 'w' 把原文件清空了
想追加却写成 'w',老数据瞬间没了。要保留用 'a'。验证一下:
with open('e.txt', 'w', encoding='utf-8') as f:
f.write('旧内容')
with open('e.txt', 'w', encoding='utf-8') as f:
f.write('新内容')
with open('e.txt', 'r', encoding='utf-8') as f:
print(f.read())
输出 新内容——「旧内容」已被覆盖。
坑 3:编码不一致,读出乱码
写的时候用一种编码、读的时候用另一种,屏幕就会出现看不懂的符号:
with open('g.txt', 'w', encoding='gbk') as f:
f.write('中文')
with open('g.txt', 'r', encoding='latin-1') as f:
print(f.read())
输出 ÖÐÎÄ——这就是「乱码」的样子。两边编码对不上,同样的字节被解读成了别的字。
encoding='utf-8'。从 Python 3.15.0 起,不写 encoding 时默认也是 utf-8;但更早的版本在 Windows 上默认用 gbk,拿到你文件的电脑装的是什么版本谁也说不准——显式写上,到哪都不乱。Python 3.15.0 已经把默认编码统一成 utf-8,读写文本文件不再「看系统脸色」;3.15.0 之前的版本则是跟着系统走:Mac / Linux 默认 utf-8,Windows 默认 gbk。拿 python --version 看一眼自己电脑装的是哪个版本;不管新版旧版,显式写上 encoding='utf-8' 都是最稳妥的习惯——你写的文件总要发给别人、也要在自己换电脑后还能打开。坑 4:read() 调了两次,第二次是空
文件对象有个「读指针」,read() 读完后指针到了末尾,再读就什么都没有:
with open('t.txt', 'w', encoding='utf-8') as f:
f.write('hello')
with open('t.txt', 'r', encoding='utf-8') as f:
print(f.read())
print(f.read())
输出:
hello
第一次打印 hello,第二次打印一个空行。想再读,要么重新 open,要么先 f.seek(0) 把指针拉回开头。
坑 5:write() 直接写数字
write() 只收字符串,传数字进去会报错:
with open('num.txt', 'w', encoding='utf-8') as f:
f.write(100) # 报错:write 只收字符串
TypeError: write() argument must be str, not int
原因:数字要先 str(100) 转成字符串再写。比如 f.write(str(总分))。
十一、报错手册
文件相关最常见的报错就这两条,照抄本机解释器原文。
with open('不存在.txt', 'r', encoding='utf-8') as f:
print(f.read())
FileNotFoundError: [Errno 2] No such file or directory: '不存在.txt'
原因:用 'r' 读一个不存在的文件。检查文件名/路径拼写,或先 os.path.exists() 判断,或改用 try...except 接住。
with open('num.txt', 'w', encoding='utf-8') as f:
f.write(100)
TypeError: write() argument must be str, not int
原因:write() 只收字符串。数字用 str(100) 转一下再写。
with open('g.txt', 'w', encoding='utf-8') as f:
f.write('中文')
with open('g.txt', 'r', encoding='gbk') as f:
print(f.read())
UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 2: illegal multibyte sequence
原因:写是 utf-8、读却用 gbk,编码对不上。统一成 encoding='utf-8' 即可。
AI 助手小贴士|读和写都学完了,来一个完整的小闭环。
打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:写一个完整小程序:读 scores.csv 统计每科平均分,再把结果写入 result.txt。逐行加中文注释,我要照着敲一遍
十二、文件操作小结
把前面所有要点压缩成一张「操作清单」,写文件前扫一遍:
| 要做的事 | 怎么写 | 提醒 |
|---|---|---|
| 读文件 | open('x.txt', 'r', encoding='utf-8') | 文件要有 |
| 写文件(覆盖) | open('x.txt', 'w', encoding='utf-8') | 会清空旧的 |
| 追加写 | open('x.txt', 'a', encoding='utf-8') | 不清空旧的 |
| 自动关文件 | 用 with open(...) as f: | 不用写 close |
| 写数字 | f.write(str(n)) | 先转字符串 |
| 判断存在 | os.path.exists('x.txt') | 先 import os |
一个综合小例子
把「一批分数求平均,结果存进文件」串起来:
import os
scores = [88, 72, 95, 60]
avg = sum(scores) / len(scores)
with open('avg.txt', 'w', encoding='utf-8') as f:
f.write('平均分是 ' + str(avg))
print('已保存,文件存在吗:', os.path.exists('avg.txt'))
输出 已保存,文件存在吗: True。
AI 助手小贴士|文件操作的报错八成和路径、编码有关。让 AI 按「可能性从高到低」帮你排查。
打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:我用 open() 打开 txt 文件报 UnicodeDecodeError。请列出可能的原因,分别给出排查方法,并解释 encoding="utf-8" 参数的作用
十三、随堂自测(10 题)
点击选项即可判分,选错的题会立刻展开解析。做完可以看到正确率和错题清单,也可以重新答题。
1打开一个文件时,为了中文不乱码,第三个常用参数通常写成?
- A.
encoding='utf-8' - B.
mode='text' - C.
code='cn' - D. 不需要写,任何版本都默认对
encoding='utf-8'。不写时的默认编码取决于版本:从 Python 3.15.0 起已统一为 utf-8,更早的版本在 Windows 上默认 gbk——写上 encoding,换哪台电脑都不会乱码。2用 open('x.txt', 'w') 打开一个已有文件再写,会发生什么?
- A. 在末尾追加新内容
- B. 原内容被清空,只留新写的
- C. 直接报错不让写
- D. 什么都不做
'w' 是「写入模式」,打开的瞬间就把原内容清空。想保留老内容要用 'a' 追加。3想「在文件末尾接着写、不覆盖原来的内容」,模式应该选?
- A.
'r' - B.
'w' - C.
'x' - D.
'a'
'a' 是 append(追加),新内容叠在老内容后面。日志、记录类文件都用它。4为什么大家都推荐用 with open(...) as f: 而不是直接 open 后手动 close()?
- A. with 运行更快
- B. with 会在块结束时自动关文件,不容易忘
- C. with 不用写文件名
- D. 两种方法完全一样
with 离开缩进块自动 close(),就算中途出错也会关,省心且不容易丢数据。5f.read() 一口气读完整篇文件,返回的是什么类型?
- A.
列表 list - B.
字典 dict - C.
字符串 str - D.
数字 int
read() 返回字符串(含换行符)。想按行成列表用 readlines(),想逐行处理用 for line in f。6用 'r' 模式去读一个根本不存在的文件,会报什么错?
- A.
ValueError - B.
TypeError - C.
FileNotFoundError - D.
PermissionError
FileNotFoundError。先核对文件名/路径,或用 os.path.exists()、try...except 提前处理。7f.write(100) 这样直接把一个整数写进文件,会怎样?
- A. 正常写进 100
- B. 写成字符串 '100'
- C. 报错,write 只收字符串
- D. 写入 0
write() 只收字符串,直接传数字会报 TypeError。要先 str(100) 转成字符串再写。8对同一个文件对象连续调用两次 f.read(),第二次会怎样?
- A. 再读一遍同样的内容
- B. 从头再读
- C. 得到一个空字符串
- D. 直接报错
read() 读完指针到了末尾,第二次读到的就是空字符串。要重读得重新 open 或 f.seek(0)。9代码里写 open('data.txt') 这种相对路径,「当前目录」指的是?
- A. 文件所在的目录
- B. 你运行这段代码的目录
- C. C 盘根目录
- D. 桌面
/tmp/tut_demo/ 验证。10有一段重要日志不想被覆盖,应该用哪种模式打开日志文件?
- A.
'w' - B.
'r' - C.
'rb' - D.
'a'
'a' 是追加,新日志叠在后面;'w' 会清空旧日志。重要数据务必用 'a'。