引言
本文将带你掌握 Python 文件操作的核心技能,并避开常见的陷阱。无论是保存程序运行结果、读取配置文件,还是处理数据,都离不开文件的读取和写入。
文件操作的核心步骤
无论是读取还是写入文件,Python 中的操作都遵循一个固定的三步流程:
本文介绍 Python 文件操作的核心流程,包括使用 open() 打开文件、with 语句自动管理上下文资源。详细讲解了读取文件的三种常用方法(read、readline、readlines)及逐行读取的最佳实践,涵盖文件不存在处理和编码问题解决方案。同时阐述了写入文件的模式(覆盖、追加、独占),演示了 write 与 writelines 方法的使用,强调换行符处理、目录创建及 pathlib 简化操作的重要性,并提供异常处理示例以确保程序健壮性。

本文将带你掌握 Python 文件操作的核心技能,并避开常见的陷阱。无论是保存程序运行结果、读取配置文件,还是处理数据,都离不开文件的读取和写入。
无论是读取还是写入文件,Python 中的操作都遵循一个固定的三步流程:
open() 函数获取一个文件对象。这个流程就像我们平时使用笔记本:先翻开本子(打开),然后在上面写字或阅读(操作),最后合上本子(关闭)。如果不关闭,可能会造成资源泄漏,甚至导致数据丢失或文件损坏。
open() 是 Python 内置的函数,基本语法如下:
file_object = open(file, mode='r', encoding=None)
file:文件路径(字符串或 Path 对象)。mode:打开模式,决定了你是要读、写还是追加。默认是 'r'(只读)。encoding:文本编码,强烈建议指定为 'utf-8',以避免中文乱码。示例:
f = open('poem.txt', 'r', encoding='utf-8')
content = f.read()
# 文件操作
f.close()
# 关闭文件
但上面的写法有一个隐患:如果在读取文件的过程中发生异常,f.close() 可能永远不会被执行,导致文件一直处于打开状态。为了解决这个问题,Python 提供了更安全的方式——with 语句。
with 语句会在代码块执行完毕后自动关闭文件,即使发生异常也不例外。语法如下:
with open('poem.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 在缩进块内操作文件
# 离开缩进后,文件自动关闭
优点:
close()。最佳实践:永远优先使用 with 语句 来操作文件。这是 Python 社区的共识,也是编写健壮代码的基本要求。
open() 的 mode 参数是一个字符串,其中常见的模式有:
| 模式 | 含义 | 文件不存在时 | 文件存在时 |
|---|---|---|---|
'r' | 只读(默认) | 报错 | 正常打开,指针在文件开头 |
'w' | 写入,会先清空文件内容 | 创建新文件 | 清空原文件内容 |
'a' | 追加,在文件末尾写入 | 创建新文件 | 指针移到文件末尾,保留原内容 |
'x' | 独占创建,如果文件已存在则报错 | 创建新文件 | 报错(FileExistsError) |
'b' | 二进制模式(可与其他模式组合,如 'rb') | - | - |
't' | 文本模式(默认,通常省略) | - | - |
注意:
会被自动转换为当前系统的换行符(Windows 为 ,Linux/macOS 为 );读取时则自动转换回来。如果你处理的是二进制文件(如图片、视频),必须使用二进制模式 'b',避免这种转换。'+' 组合,实现读写同时进行(如 'r+'),但对初学者不常用,可暂不展开。下面是一个完整的读取文件示例,包含了路径构建和异常处理:
from pathlib import Path
# 假设当前脚本在项目根目录,我们想读取 data/poem.txt
file_path = Path(__file__).parent / 'data' / 'poem.txt'
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
print(content)
except FileNotFoundError:
print(f'文件不存在:{file_path}')
except UnicodeDecodeError:
print('文件编码可能不是 UTF-8,请检查')
这个例子综合了基于 __file__ 构建路径的知识和本篇的核心步骤。
文件操作的核心步骤可以总结为:
open() 打开文件,指定路径、模式和编码。with 语句包裹操作,确保文件自动关闭。with 块内进行读取或写入。掌握了这个框架,接下来我们就可以深入探讨具体的读取方法和写入方法了。
掌握了文件操作的核心步骤后,我们来看最常用的操作——读取文件。读取文件就是将磁盘上存储的数据加载到内存中,供程序处理。Python 提供了多种读取方法,适用于不同场景。
要读取文件,首先要用 open() 函数以只读模式打开它。模式参数为 'r'(默认,可以省略)。同时,强烈建议指定文件编码,特别是当文件包含中文等非英文字符时。
# 基本写法
f = open('poem.txt', 'r', encoding='utf-8')
content = f.read()
f.close()
但正如前面所说,更安全的做法是使用 with 语句,它会自动关闭文件:
with open('poem.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 在缩进块内使用 content
# 离开缩进后文件自动关闭
接下来的所有示例都将采用 with 语句。
1. read():一次性读取整个文件
read() 方法会将文件的全部内容作为一个字符串返回。它适合读取较小的文件,因为如果文件太大(比如几个 GB),一次性读入内存可能会导致内存溢出。
with open('poem.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
假设 poem.txt 内容为:
我们之间的回忆 全部都小心地收集 我总是偷偷地哭泣 像倦鸟失了归期 但愿我相信的爱情 结局紧握在我手心 时光匆匆却没有遗失过去
输出结果就是文件的全部内容。
read() 也可以指定读取的字符数(或字节数,在二进制模式下),例如 f.read(100) 只读取前 100 个字符。
2. readline():逐行读取
readline() 每次读取一行,包括行尾的换行符( )。当文件较大时,逐行读取可以节省内存,因为每次只处理一行。
with open('poem.txt', 'r', encoding='utf-8') as f:
line = f.readline()
while line:
print(line, end='')
# 因为 line 已经包含换行符,print 默认也会换行,所以用 end='' 去掉多余的换行
line = f.readline()
输出结果与文件内容一致。
更优雅的方式是直接迭代文件对象,这本质上是调用 readline() 的简化写法(见下一节:3. 逐行读取的最佳实践)。
3. readlines():读取所有行到列表
readlines() 将文件的所有行读取到一个列表中,每一行(包括换行符)作为列表的一个元素(所以一般会和 for 循环相结合)。它也适合小文件,因为会将整个文件加载到内存。
with open('poem.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
print(line, end='')
输出结果同样与文件内容一致。
注意:readlines() 返回的列表中的每一行都保留了换行符,如果你不需要换行符,可以用 strip() 去除。
Python 的文件对象本身是可迭代的,这意味着你可以直接用 for line in f 来逐行读取文件。这种方式既简洁又内存友好,是处理大文件的推荐做法。
with open('poem.txt', 'r', encoding='utf-8') as f:
for line in f:
print(line, end='')
这比使用 while 循环调用 readline() 更简洁,也比 readlines() 更节省内存(因为不会一次加载所有行)。
如果尝试读取的文件不存在,Python 会抛出 FileNotFoundError。为了让程序更健壮,可以使用 try...except 捕获这个异常。
from pathlib import Path
file_path = Path('data') / 'poem.txt'
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
print(content)
except FileNotFoundError:
print(f'错误:文件 {file_path} 不存在,请检查路径。')
这样程序就不会直接崩溃,而是给出友好的提示。
当文件的编码与 open() 中指定的 encoding 不一致时,可能会抛出 UnicodeDecodeError 或出现乱码。例如,一个用 GBK 编码的文件如果用 utf-8 读取就会出错。
解决方案:
chardet 库自动检测编码(但初学者可以先假设文件是 UTF-8 编码)。open() 中明确指定 encoding 参数,避免依赖系统默认编码(不同操作系统默认编码可能不同)。try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except UnicodeDecodeError:
print('无法以 UTF-8 编码读取文件,请尝试其他编码如 gbk。')
读取文件后,通常需要对内容进行处理。例如,统计文件行数、单词数,或者按行解析数据。
示例:统计文件行数
with open('poem.txt', 'r', encoding='utf-8') as f:
line_count = 0
for line in f:
line_count += 1
print(f'文件共有 {line_count} 行')
示例:读取所有行并去除换行符
with open('poem.txt', 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f]
# 使用列表推导式去除换行符
print(lines)
读取文件的要点可以归纳为:
with open(...) as f 确保文件正确关闭。read() 或 readlines()for line in f)encoding='utf-8' 避免编码问题。try...except 处理文件不存在等异常情况。掌握了这些,你已经能够轻松读取各种文本文件了。下一节我们将学习如何将数据写入文件,让程序能够持久化保存结果。
学会了读取文件,接下来我们看看如何将数据写入文件。写入文件是程序持久化存储数据的基本方式,比如保存用户配置、记录日志、输出处理结果等。与读取类似,写入也遵循'打开 → 操作 → 关闭'的核心步骤,但使用的打开模式不同。
写入文件时,需要根据需求选择合适的打开模式:
| 模式 | 含义 | 文件不存在时 | 文件存在时 |
|---|---|---|---|
'w' | 写入(Write) | 创建新文件 | 清空原文件内容,再写入 |
'a' | 追加(Append) | 创建新文件 | 保留原内容,在文件末尾追加 |
'x' | 独占创建(Exclusive creation) | 创建新文件 | 报错 FileExistsError |
'w+' | 读写(先清空) | 创建新文件 | 清空原文件内容,可读写 |
'a+' | 读写(追加模式) | 创建新文件 | 保留原内容,可读写(读从头,写在尾) |
对于初学者,最常用的是 'w' 和 'a':
'w':适合需要全新写入的场景,比如程序每次运行生成新的输出文件。'a':适合需要保留历史记录的日志文件,或者希望追加内容而不覆盖原文件。⚠️ 特别注意:'w' 模式会清空文件原有内容!如果文件里有重要数据,使用前务必确认或做好备份。
写入文件主要通过两个方法:
write(data):将字符串 data 写入文件。返回写入的字符数(或字节数)。不会自动添加换行符,如果需要换行,必须手动写入 。writelines(lines):将一个字符串列表写入文件。它也不会自动添加换行符,如果列表中的字符串不包含换行符,所有内容会连在一起。示例:使用 write() 写入
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('第一行内容')
f.write('第二行内容')
# 这会在第一行后面直接接着写,没有换行
结果 output.txt 中只有一行:第一行内容第二行内容。
要分行,必须显式加入 :
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('第一行内容\n')
f.write('第二行内容\n')
示例:使用 writelines() 写入
lines = ['第一行\n', '第二行\n', '第三行\n']
with open('output.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)
注意 lines 中的每个字符串已经包含了换行符 ,如果忘记加,它们会挤在一行。
覆盖写入 ('w')
from pathlib import Path
# 构建路径(沿用上一篇的技巧)
file_path = Path(__file__).parent / 'output' / 'notes.txt'
# 确保父目录存在
file_path.parent.mkdir(parents=True, exist_ok=True)
# 写入内容(覆盖)
with open(file_path, 'w', encoding='utf-8') as f:
f.write('这是第一行。\n')
f.write('这是第二行。\n')
print(f'已写入文件:{file_path}')
每次运行这段代码,notes.txt 都会只包含最新写入的两行。
追加写入 ('a')
with open(file_path, 'a', encoding='utf-8') as f:
f.write('这是追加的一行。\n')
多次运行,文件末尾会不断添加新行,原内容保留。
独占创建写入 ('x')
try:
with open('new_file.txt', 'x', encoding='utf-8') as f:
f.write('这个文件是全新创建的。')
except FileExistsError:
print('文件已存在,无法创建。')
'x' 模式确保不会意外覆盖现有文件,适合需要原子性创建的场合。
写入文件时,如果文件路径中包含尚不存在的目录,Python 会抛出 FileNotFoundError。因此,在写入前应确保所有父目录都已存在。使用 pathlib 可以轻松完成:
from pathlib import Path
output_path = Path('data') / 'results' / 'output.txt'
# 创建所有父目录(如果不存在)
output_path.parent.mkdir(parents=True, exist_ok=True)
# 现在可以安全写入
output_path.write_text('Hello, world!', encoding='utf-8')
mkdir(parents=True, exist_ok=True) 会递归创建缺失的目录,并且如果目录已存在也不会报错。
如果你已经在使用 pathlib,可以直接调用 Path.write_text() 方法,一行搞定写入:
from pathlib import Path
file_path = Path('hello.txt')
file_path.write_text('你好,世界!', encoding='utf-8')
这个方法内部会处理文件的打开和关闭,如果文件已存在,默认会覆盖(等同于 'w' 模式)。它同样需要父目录存在,所以创建文件前最好调用 file_path.parent.mkdir(...)。
,所有内容会挤在一起。'w' 模式前,确认是否真的想清空文件。可以先用 Path.exists() 检查文件是否存在,询问用户确认。encoding='utf-8',避免其他程序读取时出现乱码。with 语句可以自动关闭,但如果你不用 with,一定要在写入后调用 f.close(),否则数据可能没有真正写入磁盘(由于缓冲)。示例:安全写入前检查
file_path = Path('important.txt')
if file_path.exists() and input('文件已存在,覆盖?(y/n)') != 'y':
print('取消写入')
else:
file_path.write_text('新内容', encoding='utf-8')
写入文件的要点:
'w'(覆盖)、'a'(追加)、'x'(独占)。with 语句确保文件正确关闭。 以实现分行。mkdir(parents=True))。encoding='utf-8'。pathlib 的 write_text() 简化操作。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online