Python 读取文件的标准写法是 with open(路径, encoding='utf-8') as f,在 with 代码块里取内容,退出代码块时文件自动关闭。小文件用 read() 一次性读成字符串最省事;大文件必须改成逐行读取,其中直接 for 迭代文件对象最省内存,一次只在内存里留一行,不会把整个文件都装进来。
很多人入门时只记住了 open() 和 read() 两个函数,等到真要处理几百兆的日志,程序一跑就卡死;或者打开一个中文文件,屏幕上全是看不懂的乱码,完全不知道从哪查起。其实这两类问题都有确定答案:内存问题靠逐行读取解决,乱码问题靠显式指定字符编码解决。本文编程狮就按 4 种读取方法逐一讲清,最后给你一份选型建议和踩坑清单。

一、读取文件到底在做什么
从磁盘到程序,读取文件事实上分成三步:打开文件拿到句柄、按字符编码把字节翻译成字符串、用完关闭句柄。新手写的代码出问题,八成出在第二步和第三步——编码没写对就乱码,句柄没关闭就可能丢数据。
1.1 一个最小可运行示例
先准备一个 note.txt,内容是三行文字(第一行是"编程狮第一行",第二行是"第二行 hello",第三行是"第三行")。下面这段代码可以直接运行:
with open('note.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
print('字符数:', len(content))
运行结果是:
编程狮第一行
第二行 hello
第三行
字符数: 21
注意两个细节。第一,encoding='utf-8' 是显式写出来的,不要省——后面第五节会专门解释为什么。第二,这里没有调用 f.close(),但文件确实关掉了,因为 with 语句会在代码块结束时(哪怕中间抛了异常)自动关掉它。这是 Python 里最该养成习惯的一条:读写文件一律用 with。
1.2 常见误解
第一个误解是"读完要手动 close()"。用 with 就不需要,手动关闭反而容易漏:一旦读取过程中抛了异常,close() 那行根本执行不到,文件句柄就一直占着。在 Windows 上,被占用的文件没法删除也没法被别的程序改写,排查起来很费时间。
第二个误解是"打开时不写编码也没事"。在你自己电脑上没事,不代表在别人电脑上没事。不写 encoding 时,Python 用的是系统本地编码,同一份代码换个环境,默认编码可能就变了。怎么查看当前的本地编码:
import locale
print(locale.getencoding()) # 中文 Windows 上通常输出 cp936
print(open('note.txt').encoding) # 本次实际生效的编码
第三个误解是"文件路径写文件名就行"。相对路径是相对于当前工作目录的,而当前工作目录未必是你的脚本所在目录——在编辑器里运行和在命令行里运行,结果可能完全不同。路径写错会得到 FileNotFoundError: [Errno 2] No such file or directory。
二、方法一:read() 一次性读完(小文件专用)
read() 不带参数时,会把整个文件读成一个字符串,换行符原样保留:
with open('note.txt', encoding='utf-8') as f:
content = f.read()
print(repr(content))
# '编程狮第一行\n第二行 hello\n第三行\n'
它的优点是拿到完整内容,后面想怎么切分、替换、正则匹配都方便,适合配置文件、模板、小型 JSON 这类确定不大的文件。
它的问题只有一个但很致命:文件多大,内存就吃多少。编程狮实测过:一个 20 万行、磁盘大小约 5.2 MB 的日志文件,用 read() 读进来之后,那个字符串在内存里约占 5.5 MB。看起来还行?那是因为文件小。换成几个 GB 的日志或者数据导出文件,内存瞬间就被吃光,程序要么被系统杀掉,要么把整台机器拖慢。判断标准很简单:文件超过几十 MB,或者你不确定它有多大,就不要用 read()。
顺带一提,read() 也可以传一个数字参数,比如 f.read(100) 表示读取 100 个字符,适合按固定长度解析的特殊格式,日常用得不多。
三、方法二:readline() 与 readlines() 两种逐行读取
3.1 readline():一次读一行
readline() 每次调用返回一行(包含行尾的换行符),配合 while 循环就能做逐行读取:
with open('note.txt', encoding='utf-8') as f:
line = f.readline()
while line:
print(repr(line))
line = f.readline()
循环条件是 while line,因为读到文件末尾时 readline() 返回的是空字符串 '',空字符串在布尔判断里是假,循环就结束了。这个细节一定要记牢,写成 while line != '' 效果一样,但千万别写成判断 None。
3.2 readlines():一次读成列表
readlines() 把每一行作为列表的一个元素返回,行数多的时候同样占内存,但拿到列表后可以随机访问、切片、统计行数:
with open('note.txt', encoding='utf-8') as f:
lines = f.readlines()
print(lines) # ['编程狮第一行\n', '第二行 hello\n', '第三行\n']
print('行数:', len(lines))
# 去掉每行末尾的换行符
clean = [line.rstrip('\n') for line in lines]
print(clean) # ['编程狮第一行', '第二行 hello', '第三行']
注意每个元素都带着 \n,直接用去做字符串比较往往会失败,记得先用 rstrip('\n') 或 strip() 处理。这个函数适合"行数不多、但需要按行号访问"的场景,比如读一个几百行的名单。
四、方法三和方法四:直接迭代文件对象与 pathlib 写法
4.1 方法三:直接 for 迭代文件对象(大文件首选)
这是处理大文件时最推荐的写法——文件对象本身就可以被迭代,每轮循环只把当前一行读进内存:
count = 0
with open('big.log', encoding='utf-8') as f:
for line in f:
count += 1
if '199999' in line:
print('命中:', line.rstrip())
print('总行数:', count)
编程狮用上面这段代码实测一个 20 万行、约 5.2 MB 的日志文件,输出是 总行数: 200000,而且内存占用几乎不随文件增大而增长——因为任何时刻内存里只有一行。哪怕文件有 10 GB,这段代码也能稳定跑完,只是慢一点。
写法上它比 readline() 的 while 循环更短,也不用自己判断什么时候结束,是逐行读取的默认选择。需要行号时可以配合 enumerate(f, 1)。
4.2 方法四:pathlib 的现代写法
Python 3.4 之后有了 pathlib,用面向对象的写法处理路径,读文本文件可以一句话搞定:
from pathlib import Path
path = Path('note.txt')
text = path.read_text(encoding='utf-8')
print(repr(text))
# 直接按行切分,不带换行符
print(text.splitlines()) # ['编程狮第一行', '第二行 hello', '第三行']
Path.read_text() 内部帮你完成了打开、读取、关闭,简洁程度最高,而且 Path 对象处理路径拼接、判断文件是否存在都很顺手(比如 path.exists())。但它本质上还是一次性读全部内容,所以大文件依然不能用它,得回到 4.1 的迭代写法:
from pathlib import Path
with Path('big.log').open(encoding='utf-8') as f:
for line in f:
pass # 在这里处理每一行
到这里已经能看出,读取文件这件事的选择,主要看你面对的是什么规模的数据。想把文件操作、异常处理和路径处理连成体系地学一遍,Python3 教程 里有按场景组织的完整示例。
五、怎么选:4 种方法对比与大文件、字符编码踩坑清单
4 种读取文件的方法讲完了,下面这张表按"内存占用"和"返回结果"两个维度把它们摆在一起,方便你对号入座:
| 方法 | 返回什么 | 内存占用 | 适用场景 |
|---|---|---|---|
read() |
整个文件的字符串 | 等于文件大小 | 几十 MB 以内的小文件 |
readline() 循环 |
每次一行字符串 | 一行 | 需要手动控制读取节奏 |
readlines() |
每行组成的列表 | 等于文件大小 | 行数不多、要按行号访问 |
| 迭代文件对象 | 每次一行字符串 | 一行 | 大文件、日志逐行处理,默认首选 |
Path.read_text() |
整个文件的字符串 | 等于文件大小 | 现代写法,小文件图省事 |
一句话决策:小文件用 read() 或 Path.read_text(),图省事;大文件、或者行数不确定,一律用 for 迭代文件对象。
接着是四个高频坑,按"踩中概率"排序:
- 字符编码不一致导致乱码或报错。 用 UTF-8 存的文件,拿 GBK 去解,轻则乱码,重则直接
UnicodeDecodeError: 'gbk' codec can't decode byte...。在中文 Windows 上,系统本地编码通常是 GBK(cp936),而 Linux 和 macOS 通常是 UTF-8,所以永远显式写encoding='utf-8',别偷懒。 - 文件路径写错。 报
FileNotFoundError先检查三件事:文件名拼对了吗、扩展名写了吗(Windows 默认可能隐藏.txt)、当前工作目录是不是你以为的那个。实在不确定就用绝对路径,Windows 上记得写成r'C:\data\note.txt'(前面加 r 防止反斜杠被当成转义)。 - 忘记关闭文件。 用
with就没有这个问题;如果因为特殊情况必须手动open(),就要配try...finally保证close()一定执行。 - 大文件内存爆掉。 见上表,
read()和readlines()都会把整个文件装进内存,逐行迭代不会。
文件读进来之后,通常还要做清洗、去重、统计这些后续操作,这一串流程在 Python3 教程 的文件与数据章节里有更完整的串联示例。

总结
读取文件只需要记住三句话:一律用 with 打开文件,让 Python 自动关闭;一律显式写 encoding='utf-8',避免换台机器就乱码;大文件一律用 for 迭代文件对象逐行处理,别一次性读进来。 剩下的 read()、readline()、readlines()、Path.read_text() 只是按场景挑顺手的工具。
with语句保证文件一定被关闭,哪怕中途抛异常;- 不写编码时用的是系统本地编码,中文 Windows 常见是 GBK,与 UTF-8 混用必出问题;
- 逐行迭代文件对象内存占用恒定,是处理大文件的默认选择;
readlines()的每个元素都带换行符,比较前先rstrip('\n')。
延伸学习
- Python 基础配套课程 —— 从变量与字符串一路讲到文件读写实战。
- Python 文件读写笔记 —— 打开文件模式、写入与追加的对照说明。
- 在线运行 Python 代码工具 —— 不用装环境,直接验证本文的每段示例代码。
常见问题
Q:为什么我打开中文文件全是乱码,或者报 UnicodeDecodeError?
A:绝大多数情况是编码不匹配。文件是 UTF-8 存的,你却用 GBK 去解(或者反过来)。解决办法是读写两侧保持同一种编码:保存文件时选 UTF-8,读取时显式写 encoding='utf-8'。如果文件已经是用 GBK 存的旧数据,那就把读取处的编码改成 encoding='gbk',并在后续处理中统一转成 UTF-8。
Q:readlines() 和直接迭代文件对象,效果不是一样吗?
A:结果看着像,内存行为完全不同。readlines() 会一次性把所有行读进内存并组装成列表,文件多大内存就占多少;直接迭代文件对象是边读边处理,任何时刻只有一行在内存里。几 MB 以内的文件两者都无所谓,超过几十 MB 就必须用迭代写法。
Q:为什么我写的文件名明明存在,却报 FileNotFoundError?
A:先确认相对路径的基准。相对路径是相对于"当前工作目录"解析的,而这个目录通常是你运行命令时所在的目录,不是脚本文件所在的目录。在编辑器里直接点运行和在终端里 cd 到别处再运行,结果可能完全不同。可以用 import os; print(os.getcwd()) 打印当前工作目录来核对,或者干脆改用绝对路径。
Q:with 代码块里还要写 f.close() 吗?
A:不需要,也不应该写。with 语句的作用就是保证退出代码块时自动关闭文件,即使中间发生了异常也一样。手动再写一次 close() 属于多余操作;真正要避免的是不用 with、直接在代码末尾写 close() 那种写法,因为一旦中途抛异常,那行代码永远不会执行。

免费 AI IDE



