用Python处理数据、日志、配置文件,几乎都绕不开文件操作。Python3在编码处理上和Python2有本质区别,所有字符串默认以Unicode表示,文件读写时需要显式或隐式地指定编码方式,否则很容易出现乱码或者直接抛出解码异常。这篇文章从open函数讲起,把文件的读、写、追加、二进制处理逐一演示,并解释with语句背后的机制,帮助你建立一套规范的文件操作习惯。

一、open函数与文件打开模式
Python3中操作文件的入口是内置函数open(),它返回一个文件对象,后续的读写都基于这个对象进行。先看一下函数签名中最重要的几个参数:file是文件路径,mode是打开模式,encoding指定文本编码,buffering控制缓冲策略。其中mode参数决定了后续能对这个文件做什么。
常见的模式字符串包括:r表示只读(默认值),文件必须存在否则报错;w表示写入,会清空原文件内容,文件不存在则创建;a表示追加,新内容写在文件末尾;b表示以二进制模式打开,可以和前面几种组合成rb、wb、ab;+表示读写兼备,例如r+既可读又可写。这些模式可以按需组合,但要记住一点:文本模式和二进制模式不能混用行为,文本模式返回字符串,二进制模式返回bytes。
# 基本的打开方式
f = open('data.txt', 'r', encoding='utf-8')
content = f.read()
print(content)
f.close() # 手动关闭,容易忘记
# 更推荐的写法:with语句自动管理
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
# 离开with代码块后文件自动关闭
encoding参数在Windows上尤其值得注意。Windows系统默认编码通常是GBK,如果在打开一个UTF-8编码的中文文本时没有指定encoding,就可能出现UnicodeDecodeError或者读出一堆乱码。因此建议无论在什么平台,只要以文本模式处理非ASCII内容,都显式写上encoding='utf-8',这是最稳妥的做法。
二、文件读取的几种方式
读取文件内容有四个常用方法,适用的场景各不相同。read()一次性读出全部内容,适合小文件;readline()每次读一行;readlines()把所有行读成一个列表;还可以直接用for循环迭代文件对象,这是内存占用最低的方式,因为它逐行读取,不会把整个文件加载进内存。
with open('data.txt', 'r', encoding='utf-8') as f:
# 方式一:读取全部内容
text = f.read()
# 方式二:读取一行
line = f.readline()
# 方式三:读取所有行为列表
lines = f.readlines()
# 方式四:逐行迭代(推荐处理大文件)
for line in f:
print(line.strip())
对于超大文件,比如几个GB的日志文件,绝对不要用read()或readlines(),它们会把整个文件塞进内存,轻则程序卡死,重则系统崩溃。正确姿势是for循环迭代,配合分批处理。如果需要更精细的控制,read(size)可以指定每次读取的字符数,实现分块读取。另外,文件对象维护着一个内部指针,读写都从指针位置开始,seek(0)可以把指针重置到文件开头,tell()则返回当前指针位置,在需要重复读取同一文件时非常有用。
还有一个容易被忽略的细节:以文本模式打开时,Python会自动做换行符转换,Windows下的\r\n会被统一成\n。而二进制模式不做任何转换,读到什么就是什么。处理图片、压缩包这类非文本文件时,必须使用二进制模式,否则内容会被破坏。
三、文件写入与追加操作
写入文件主要用write()和writelines()两个方法。注意writelines()并不会自动在元素之间添加换行符,需要自己保证每个字符串末尾带有\n,这是初学者最容易踩的坑之一。此外,写入操作并不是立即落盘的,数据先进入缓冲区,可以调用flush()强制刷新,或者干脆等文件正常关闭时自动写入。
# 覆盖写入
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('第一行内容\n')
f.write('第二行内容\n')
# 追加写入
with open('output.txt', 'a', encoding='utf-8') as f:
f.writelines(['追加的一行\n', '再追加一行\n'])
# 二进制写入,例如复制图片
with open('source.jpg', 'rb') as src, open('copy.jpg', 'wb') as dst:
while True:
chunk = src.read(1024 * 1024) # 每次读1MB
if not chunk:
break
dst.write(chunk)
上面的图片复制代码展示了二进制模式的典型用法:分块读取加写入,即使文件很大也不会占用过多内存。顺便提一句,Python允许在一个with语句中同时管理多个文件,用逗号分隔即可,写起来比嵌套两层with清爽得多。
要特别警惕'w'模式的破坏性。一旦以写模式打开已有文件,原内容立刻被清空,哪怕你一行代码都没写。如果只是想更新文件,用'r+'更安全;如果不确定文件是否存在又想避免误删,可以先通过os.path.exists()判断,或者直接用追加模式配合定位操作。
四、with语句的原理与常见避坑点
with语句能自动关闭文件,靠的是上下文管理器协议。文件对象实现了__enter__和__exit__两个方法:进入with代码块时调用前者返回文件对象,离开时无论代码块是正常执行完还是抛了异常,后者都会被调用,在__exit__内部完成文件关闭。这就保证了即使中途发生异常,文件句柄也能被正确释放,不会造成资源泄漏。
如果不用with,就得手动调用close(),并且要用try...finally包住操作代码,一旦忘记关闭,文件可能长时间占用句柄,写入的数据也可能因为缓冲未刷新而丢失。下面是几个日常开发中高频出现的坑以及对应的解决办法:
- 编码报错:读写中文文件一律显式指定encoding,读未知编码的文件可尝试
encoding='utf-8', errors='ignore'或errors='replace'规避异常,但乱码问题还是要靠确认真实编码解决。 - 路径问题:Windows路径中的反斜杠如
C:\Users\test,在字符串里建议写成原始字符串r'C:\Users\test',或者用os.path.join()和pathlib.Path拼接路径,跨平台且不易出错。 - 重复打开浪费性能:需要多次读写时,可以在同一个with块内用
seek()移动指针,而不是反复打开关闭文件。 - 判断文件是否存在:写入前用
os.path.exists()或pathlib.Path.exists()检查,避免误覆盖重要数据。
from pathlib import Path
# 推荐的路径处理方式
p = Path(r'C:\data') / 'logs' / 'app.log'
if not p.exists():
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text('日志初始化\n', encoding='utf-8')
# 追加内容
with p.open('a', encoding='utf-8') as f:
f.write('新的一条日志\n')
总的来说,Python3的文件操作并不复杂,核心就三点:用with语句管理生命周期,文本模式显式指定编码,大文件分块或逐行处理。把这三条原则贯彻到位,绝大多数文件读写问题都能提前避免。pathlib模块作为较新的路径处理工具,也建议优先使用,它把路径操作封装成对象方法,可读性和安全性都比传统的字符串拼接好不少。
Python3文件操作Python读写文件with语句修改时间:2026-09-15 19:30:40