文件读写
作为Python日常开发中最常见的IO场景之一,文件读写看似简单,细节却能决定代码的健壮性、效率甚至安全性——今天咱们就用一篇短文,把它讲明白,从基础API到避坑指南全搞定!
文件操作基础
Python 提供了内置的 open() 函数完成核心的文件操作,它返回一个文件对象/文件描述符——这是连接Python代码和操作系统磁盘文件的“桥梁”。
现代操作系统为了安全性和稳定性,禁止程序直接读写磁盘扇区,所有文件操作都必须经过它提供的统一API包装,这也是为什么我们不能直接用内存地址读取文件的原因。
读取文件
基础打开与异常处理
用 open() 读取文件的默认模式是 'r'(文本读取),但如果路径错误或文件不存在,Python 会直接抛出 FileNotFoundError 打断程序:
# 基础读取打开
f = open('/path/to/your/file.txt', 'r')
为了让程序更健壮,务必加上基础异常捕获:
try:
f = open('/path/to/nonexistent.txt', 'r')
except FileNotFoundError as e:
print(f"文件不存在,请检查路径:{e}")
四种常用读取内容的方式
成功打开后,根据文件大小、读取需求,选择对应的方法:
- 一次性全读:适合小文件(比如几十KB以内)
content = f.read() # 读取整个文件到单个字符串
- 按字节/字符分段读:大文件友好,避免一次性加载占满内存
chunk = f.read(1024) # 文本模式读1024个字符,二进制读1024字节
- 逐行单读:需要根据每行逻辑做处理时常用
line = f.readline() # 读取一行,包含末尾的换行符\n
- 全读进列表:后续要多次操作行内容时方便
lines = f.readlines() # 每一行(含\n)作为列表的一个元素
安全释放资源的两种方法
文件打开后会占用操作系统的文件描述符资源(每个进程的数量有限,比如Linux默认1024个),用完必须关闭!
- 手动关闭:容易忘,出错(比如读写抛异常)时会跳过
close()
f = open('/path/to/file.txt', 'r')
content = f.read()
f.close() # 必须放在try/except的finally块里才绝对安全
with 自动管理(强烈推荐):利用上下文管理器,代码块结束时自动触发 close(),哪怕中间抛异常
with open('/path/to/file.txt', 'r') as f:
content = f.read()
# 这里已经自动关闭,f变量失效
文件对象类型
Python 支持三类核心的“文件-like”对象:
- 磁盘文本文件:默认打开方式,按字符/字符串处理,会自动解码/编码
with open('note.txt', 'rt') as f: # 't'可省略
pass
- 磁盘二进制文件:处理图片、视频、压缩包等非文本内容,按字节/字节串处理
with open('cat.jpg', 'rb') as f:
img_bytes = f.read()
- 内存文件:完全在内存中模拟文件操作,无需磁盘IO,速度极快,适合临时数据
from io import StringIO, BytesIO
# 文本内存流
text_stream = StringIO("临时的博客草稿")
text_stream.write("\n再加一行修改")
print(text_stream.getvalue()) # 不用with也能直接取所有内容
text_stream.close()
# 二进制内存流
binary_stream = BytesIO(b'\x48\x65\x6c\x6c\x6f') # 对应"Hello"
处理不同编码的文本文件
默认Python 3用 UTF-8 编码读写文本,但Windows上经常遇到GBK/GB2312的旧文件,直接读会报 UnicodeDecodeError:
# 指定编码读取GBK文件
with open('old_chinese_note.txt', 'r', encoding='gbk') as f:
content = f.read()
如果文件有少量乱码(比如非法UTF-8字符),可以用 errors 参数调整策略:
errors='ignore':忽略非法字符
errors='replace':用 � 替换非法字符
errors='strict':默认,直接报错(最安全,提醒你文件有问题)
# 遇到乱码就替换
with open('mixed_encoding.txt', 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
写入文件
两种基础写入模式
写入操作的核心是避免误覆盖数据,先看两种最常用的模式:
- 覆盖写入(
'w'):如果文件不存在会自动创建,存在则清空所有内容重新写
with open('new_diary.txt', 'w', encoding='utf-8') as f:
f.write("今天学了Python文件读写!")
- 追加写入(
'a'):文件不存在自动创建,存在则从文件末尾开始写
with open('new_diary.txt', 'a', encoding='utf-8') as f:
f.write("\n明天继续学CSV/JSON读写!")
写入多行的两种方式
直接循环调用 f.write() 或者用更简洁的 f.writelines():
lines = ["第一行", "第二行", "第三行"]
# 方式1:手动加换行符循环写
with open('multi_line.txt', 'w', encoding='utf-8') as f:
for line in lines:
f.write(f"{line}\n")
# 方式2:用生成器表达式批量传值(更Pythonic)
with open('multi_line.txt', 'w', encoding='utf-8') as f:
f.writelines(f"{line}\n" for line in lines)
⚠️ 注意:f.writelines() 不会自动加换行符!必须自己手动拼接。
文件模式速查表
常见组合示例:
'rb':读取图片、压缩包等二进制文件
'w+':读写(覆盖式,读完要手动移动文件指针才能重读)
'a+':读写(追加式,读完同样要移动指针)
避坑与最佳实践
- 永远默认加
encoding='utf-8':避免跨平台(Windows→Linux/macOS)时的编码问题
- 永远用
with 语句:忘记手动关闭文件是新手最容易犯的低级错误
- 大文件必须逐行/分段迭代:比如GB级的日志文件,用
for line in f: 比 f.readlines() 内存友好100倍
with open('10GB_server.log', 'r', encoding='utf-8') as f:
for line in f: # 迭代文件对象默认是逐行读
if "ERROR" in line:
print(line.strip())
- 用
'x' 模式避免覆盖关键数据:比如写用户配置文件时,防止不小心把旧配置删了
try:
with open('user_config.json', 'x', encoding='utf-8') as f:
f.write('{"theme": "dark"}')
except FileExistsError:
print("配置文件已存在,无需创建")
快速练习:读取系统时区文件
用刚学的知识写个小脚本,读取Linux/macOS常用的时区文件:
import os
TIMEZONE_PATH = '/etc/timezone'
if __name__ == '__main__':
if not os.path.exists(TIMEZONE_PATH):
print("当前系统没有找到 /etc/timezone 文件(可能是Windows)")
exit(1)
try:
with open(TIMEZONE_PATH, 'r', encoding='utf-8') as f:
print(f"当前系统时区:{f.read().strip()}") # strip()去掉末尾的换行
except PermissionError:
print("没有权限读取 /etc/timezone 文件,请用sudo运行")
总结
今天我们梳理了Python文件读写的核心知识点:
- 用
open() 加 with 上下文管理器安全打开/关闭文件
- 区分文本/二进制/内存文件的使用场景
- 处理编码问题的方法
- 覆盖/追加/独占创建三种基础写入模式
- 大文件处理的内存友好技巧
掌握这些,日常开发中的90%文件IO需求都能轻松搞定!下一篇我们聊聊更高级的 CSV/JSON/Excel 结构化文件读写~