【第7章 I/O编程与异常】Python 文件操作全面详解:从基础到进阶(附丰富实例)v1.2
Python 文件操作全面详解:从基础到进阶(附丰富实例)
文件操作是 Python 编程中最核心的技能之一,涵盖文件的创建、读写、关闭、删除、属性修改等场景。本文将系统梳理 Python 文件操作的核心概念、基础 API、进阶技巧和最佳实践,结合 20+ 个可直接运行的实例,从入门到精通掌握文件操作的方方面面。
一、文件操作核心概念
在开始代码实践前,先明确几个关键概念:
| 概念 | 含义 |
|---|---|
| 文件路径 | 文件在系统中的位置(绝对路径:如 C:/data/file.txt;相对路径:如 ./data/file.txt) |
| 文件模式 | 打开文件的方式(读、写、追加等,决定操作权限) |
| 文件对象 | 打开文件后返回的对象,用于后续读写操作(通过 open() 函数获取) |
| 缓冲区 | 内存中的临时区域,用于高效读写(默认开启,可手动控制) |
| 编码格式 | 文本文件的字符编码(如 UTF-8、GBK,决定能否正确读写中文等特殊字符) |
二、基础文件操作:open() 与文件对象
open() 函数是文件操作的入口,用于打开文件并返回文件对象。掌握它的参数和文件对象的方法,就能完成 80% 的日常任务。
2.1 open() 函数参数详解
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
| 核心参数 | 作用说明 | 常用取值 |
|---|---|---|
file |
必需,文件路径(字符串或 Path 对象) |
如 'data.txt'、Path('data/file.txt') |
mode |
打开模式(决定读写权限和文件类型) | 文本模式:'r'(读)、'w'(写)、'a'(追加)、'r+'(读写);二进制模式: 'rb'、'wb'、'ab'(加 b 表示二进制) |
encoding |
文本模式下的编码格式(二进制模式无需指定) | 'utf-8'(推荐,支持中文)、'gbk'(中文Windows默认) |
2.2 文件模式总表
| 分类 | 完整模式 | 默认缩写(常用形式) | 数据格式 | 读写权限 | 文件存在性处理 | 初始指针位置 | ⚠️ 易错点 |
|---|---|---|---|---|---|---|---|
| 只读 | rt |
r(默认) |
文本 | 只读 | 必须存在(否则报错) | 开头(0) | 不可写,文件不存在抛FileNotFoundError |
rb |
rb |
二进制 | 只读 | 必须存在(否则报错) | 开头(0) | 非文本文件(图片/视频)必用,否则数据损坏 | |
| 只写 | wt |
w(默认) |
文本 | 只写 | 不存在则创建,存在则清空 | 开头(0) | 覆盖原有内容,谨慎使用(不可恢复) |
wb |
wb |
二进制 | 只写 | 不存在则创建,存在则清空 | 开头(0) | 需传入bytes类型,字符串需用encode()转换 |
|
| 追加 | at |
a(默认) |
文本 | 只写 | 不存在则创建,存在则保留 | 末尾 | 无论指针位置,写入始终在末尾 |
ab |
ab |
二进制 | 只写 | 不存在则创建,存在则保留 | 末尾 | 适合日志追加、分片文件拼接 | |
| 读写(保留原内容) | rt+ |
r+(默认) |
文本 | 读写 | 必须存在(否则报错) | 开头(0) | 写入覆盖当前指针位置内容,需用seek()控制指针 |
rb+ |
rb+ |
二进制 | 读写 | 必须存在(否则报错) | 开头(0) | 精确修改二进制数据(如图片元数据),需seek()定位字节 |
|
| 读写(清空原内容) | wt+ |
w+(默认) |
文本 | 读写 | 不存在则创建,存在则清空 | 开头(0) | 读前需seek(0)(写入后指针在末尾),清空不可逆 |
wb+ |
wb+ |
二进制 | 读写 | 不存在则创建,存在则清空 | 开头(0) | 新建并编辑二进制文件(如协议数据包) | |
| 追加+读 | at+ |
a+(默认) |
文本 | 读写 | 不存在则创建,存在则保留 | 末尾 | 读前需seek(0)(默认在末尾,直接读返回空) |
ab+ |
ab+ |
二进制 | 读写 | 不存在则创建,存在则保留 | 末尾 | 追加后验证文件完整性(如校验MD5) |
核心缩写规则(一句话看懂)
- 文本模式(
t)可省略,默认缩写为核心操作符(如rt→r,wt+→w+)。 - 二进制模式(
b)必须显式写出,无缩写(如rb、ab+需完整写)。
2.3 基础操作流程:打开 → 操作 → 关闭
文件操作必须遵循“打开-操作-关闭”的流程,否则可能导致资源泄露。推荐用 with 语句(上下文管理器)自动关闭文件,无需手动调用 close()。
示例 1:文本文件读写(基础流程)
# 1. 写入文件('w' 模式)
with open('test.txt', 'w', encoding='utf-8') as f: # with 语句自动关闭文件
f.write('Hello, Python!\n') # 写入字符串,\n 表示换行
f.write('这是中文内容\n') # 需指定 encoding 才能正确写入中文
# 2. 读取文件('r' 模式)
with open('test.txt', 'r', encoding='utf-8') as f:
content = f.read() # 读取全部内容
print('文件内容:\n', content)
# 输出:
# 文件内容:
# Hello, Python!
# 这是中文内容
示例 2:二进制文件操作(如图片、音频)
二进制文件(非文本文件)需用 'rb'/'wb' 模式,且无需指定 encoding:
# 复制图片(二进制操作)
with open('source.jpg', 'rb') as src, open('copy.jpg', 'wb') as dst:
data = src.read() # 读取二进制数据
dst.write(data) # 写入二进制数据
print('图片复制完成')
三、文件读取的 5 种方式
根据需求不同,文件读取有多种方式,灵活选择可提高效率(尤其处理大文件时)。
简表
| 方法 | 功能说明 | 适用场景 |
|---|---|---|
| read() | 无参:读取文件全部内容;有参 n:读取至多 n 字节(二进制模式常用,文本模式需谨慎) |
小文件(一次性处理全文);二进制大文件分块处理(如网络传输、分片复制) |
| readline() | 读取一行内容(含换行符 \n),支持指定字节限制 limit(Python 3.2+) |
按行处理文件(如日志解析、CSV逐行读取);超大行文件(指定 limit 控制内存) |
| readlines() | 读取所有行,返回列表(每行作为元素,含换行符 \n) |
中等大小文件,需按行索引访问(如按行号提取内容)或批量处理所有行 |
迭代文件对象(for line in f) |
自动逐行读取,循环内每次返回一行(含换行符 \n),底层调用 readline() |
大/超大文件(内存友好,无需一次性加载全部内容),仅需顺序处理行数据 |
详细表
| 读取方式 | 核心行为 | 返回值类型 | 内存占用 | 效率(大文件) | 操作灵活性 | 编码兼容性 | 文件指针影响 | 异常处理场景 | 适用文件类型 | 代码简洁度 | 随机访问支持 | Python版本兼容性 | 典型示例场景 | 扩展用法 | 关键注意事项 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
f.read() |
无参:读全文件; 有参 n:读至多 n 字节(不足返回剩余) |
文本模式:str(换行符统一为 \n)二进制模式: bytes(原始字节流) |
高(等同文件大小) | 低(易内存溢出) | 低(一次性获取) | 文本模式:支持多字节编码,但 n 落在字符中间抛 UnicodeDecodeError |
读取后指针位于文件尾; 支持 f.seek(0) 重置 |
1. FileNotFoundError(文件不存在);2. UnicodeDecodeError(编码不匹配);3. MemoryError(大文件) |
小型文本/二进制文件 | ★★★ | 不支持(需手动计算偏移量) | 全版本支持; 文本模式 n 特性无版本限制 |
读取小型配置文件、全文关键词提取(if "error" in f.read()) |
配合 re.findall() 批量匹配正则;二进制模式下配合 struct.unpack() 解析二进制结构 |
1. 文本模式自动转换换行符; 2. 大文件禁用,避免内存溢出; 3. 二进制模式保留原始字节 |
f.readline(limit=None) |
默认读一行(\n 分隔);指定 limit:读至多 limit 字节(超限截断) |
文本模式:str(含行尾 \n)二进制模式: bytes(含行尾 \n) |
低(仅当前行/limit 字节) |
中(手动循环开销) | 高(可中断) | 文本模式:支持多字节编码,limit 不截断字符(抛异常) |
读取后指针移至下一行首; 支持 f.seek(0) 重置 |
1. UnicodeDecodeError(编码不匹配);2. 超大行无 limit 时抛 MemoryError |
文本文件、超大行文件(指定 limit) |
★★ | 支持(需手动记录指针) | 3.2+ 支持 limit 参数;低版本无该参数 |
按行解析日志(while line := f.readline(): if "ERROR" in line: break) |
配合 itertools.islice() 跳过前N行;指定 limit=1024 拆分超大行 |
1. 末尾返回 ""/b"";2. 无 limit 时超大行风险;3. 需手动写循环 |
f.readlines(hint=None) |
一次性读所有行,返回列表; 指定 hint:读约 hint 字节(减少I/O) |
文本模式:list[str](含 \n)二进制模式: list[bytes](含 \n) |
高(文件大小+列表开销) | 低(易内存溢出) | 中(支持索引) | 文本模式:支持多字节编码,无截断风险 | 读取后指针位于文件尾; 支持 f.seek(0) 重置 |
1. UnicodeDecodeError(编码不匹配);2. MemoryError(大文件);3. 列表索引越界 |
中等大小文本/二进制文件 | ★★★ | 支持(列表索引访问) | 全版本支持;hint 参数无版本限制 |
中等CSV文件按行号处理(lines = f.readlines(); print(lines[10])) |
配合 list comprehensions 批量过滤([line for line in f.readlines() if "OK" in line]);用 f.writelines() 写回修改后内容 |
1. 大文件禁用; 2. 元素含 \n,需手动处理;3. hint 仅为参考,可能多读一行 |
for line in f(迭代) |
自动逐行读取(底层调用 readline()),含行尾 \n |
文本模式:str(单行长字符串)二进制模式: bytes(单行长字节串) |
极低(仅当前行) | 极高(内存友好) | 中(顺序处理) | 文本模式:完美支持多字节编码,无截断风险 | 迭代后指针位于文件尾; 支持 f.seek(0) 重置 |
1. UnicodeDecodeError(编码不匹配);2. 迭代中中断无资源泄漏 |
大/超大文本文件、日志文件 | ★★★ | 不支持(需手动缓存) | 全版本支持; 迭代优化无版本限制 |
处理GB级日志(count = sum(1 for line in f if "critical" in line)) |
配合 enumerate 获取行号(for idx, line in enumerate(f, 1));用 itertools.takewhile() 条件终止迭代 |
1. 重置指针需 f.seek(0);2. 无法直接随机访问; 3. 内存效率最优 |
f.read(n) |
精确读至多 n 字节(不足返回剩余);文本模式保证字符完整性 |
文本模式:str(长度≤n)二进制模式: bytes(长度≤n) |
低-中(由 n 控制) |
高(分块高效) | 极高(自定义块) | 文本模式:支持多字节编码,但 n 不足字符字节时返回空串 |
读取后指针移至 n 字节处;支持 f.seek(0) 重置 |
1. UnicodeDecodeError(编码不匹配);2. 二进制模式下分块不完整导致解析错误 |
二进制文件、大文件分片传输 | ★★ | 支持(按字节偏移量) | 全版本支持; 文本模式字符完整性无版本限制 |
二进制文件分片传输(while chunk := f.read(4096): socket.send(chunk)) |
配合 hashlib 分块计算文件MD5;用 shutil.copyfileobj() 分块复制文件 |
1. 文本模式禁用(避免字符拆分); 2. 二进制模式 n 建议设为4096/8192(磁盘I/O块大小);3. 需循环判断末尾 |
示例 3:5 种读取方式对比
# 先创建测试文件
with open('read_demo.txt', 'w', encoding='utf-8') as f:
f.write('第一行内容\n')
f.write('第二行内容\n')
f.write('第三行内容\n')
# 1. read():读取全部内容
with open('read_demo.txt', 'r', encoding='utf-8') as f:
print('1. read() 结果:\n', f.read())
# 2. readline():逐行读取
with open('read_demo.txt', 'r', encoding='utf-8') as f:
print('\n2. readline() 结果:')
print(f.readline(), end='') # 第一行(end='' 避免重复换行)
print(f.readline(), end='') # 第二行
# 3. readlines():读取所有行到列表
with open('read_demo.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
print('\n3. readlines() 结果:', lines)
print('按索引访问第二行:', lines[1])
# 4. 迭代文件对象(最推荐的大文件处理方式)
with open('read_demo.txt', 'r', encoding='utf-8') as f:
print('\n4. 迭代文件对象:')
for line in f:
print(f'行内容:{line}', end='')
# 5. read(n):读取指定字节数(文本模式下约等于字符数,非精确)
with open('read_demo.txt', 'r', encoding='utf-8') as f:
print('\n\n5. read(n) 结果:')
print(f.read(5)) # 读取前5个字符
print(f.read(3)) # 继续读取3个字符
输出结果:
1. read() 结果:
第一行内容
第二行内容
第三行内容
2. readline() 结果:
第一行内容
第二行内容
3. readlines() 结果: ['第一行内容\n', '第二行内容\n', '第三行内容\n']
按索引访问第二行: 第二行内容
4. 迭代文件对象:
行内容:第一行内容
行内容:第二行内容
行内容:第三行内容
5. read(n) 结果:
第一行内
容
四、文件写入的 3 种方式
写入操作需注意模式选择('w' 覆盖 vs 'a' 追加),以及换行符的处理。
| 方法 | 功能说明 |
|---|---|
write(str) |
写入字符串,返回写入的字符数 |
writelines(seq) |
写入字符串序列(列表/元组等),不自动添加换行符 |
print(..., file=f) |
用 print 函数写入,可自动添加换行符(end 参数控制) |
示例 4:3 种写入方式对比
# 1. write():写入字符串
with open('write_demo.txt', 'w', encoding='utf-8') as f:
count1 = f.write('用write写入第一行\n') # 需手动加换行符
count2 = f.write('用write写入第二行')
print(f'write写入字符数:{count1}, {count2}') # 输出:11, 9
# 2. writelines():写入序列(无自动换行)
with open('write_demo.txt', 'a', encoding='utf-8') as f: # 'a' 追加模式
lines = ['\n用writelines写入第三行', '用writelines写入第四行'] # 无换行符
f.writelines(lines) # 不会自动加换行,内容会连在一起
# 3. print() 函数写入(自动换行)
with open('write_demo.txt', 'a', encoding='utf-8') as f:
print('用print写入第五行', file=f) # 自动加换行符
print('用print写入第六行', file=f, end='---') # 自定义结尾符
# 查看结果
with open('write_demo.txt', 'r', encoding='utf-8') as f:
print('\n最终文件内容:\n', f.read())
输出结果:
write写入字符数:11, 9
最终文件内容:
用write写入第一行
用write写入第二行
用writelines写入第三行用writelines写入第四行
用print写入第五行
用print写入第六行---
五、文件指针与随机访问
文件对象内部有一个“指针”,记录当前读写位置。通过移动指针,可实现随机读写(如修改文件中间内容)。
| 方法/属性 | 功能说明 |
|---|---|
tell() |
返回当前指针位置(字节数) |
seek(offset, whence) |
移动指针到指定位置:offset 偏移量(正数向后,负数向前);whence 基准位置(0:文件开头,1:当前位置,2:文件末尾) |
示例 5:文件指针操作
with open('pointer_demo.txt', 'w+', encoding='utf-8') as f: # 'w+' 读写模式
f.write('0123456789') # 写入10个字符
print('写入后指针位置:', f.tell()) # 输出:10(在文件末尾)
# 移动指针到开头( whence=0 表示从文件开头计算)
f.seek(0, 0)
print('移动到开头后读取:', f.read(3)) # 读取前3个字符:012
print('当前指针位置:', f.tell()) # 输出:3
# 从当前位置向后移动2个字符
f.seek(2, 1) # whence=1 表示从当前位置计算
print('移动后读取:', f.read(2)) # 读取位置5-6的字符:56
print('当前指针位置:', f.tell()) # 输出:7
# 从文件末尾向前移动3个字符
f.seek(-3, 2) # whence=2 表示从文件末尾计算
print('从末尾移动后读取:', f.read()) # 读取最后3个字符:789
输出结果:
写入后指针位置: 10
移动到开头后读取: 012
当前指针位置: 3
移动后读取: 56
当前指针位置: 7
从末尾移动后读取: 789
六、文件属性与元数据
通过 os 或 pathlib 库可获取文件的元数据(大小、创建时间、修改时间等)。
示例 6:获取文件属性
from pathlib import Path
import time
file_path = Path('test.txt')
# 确保文件存在
if not file_path.exists():
with open(file_path, 'w') as f:
f.write('测试文件属性')
# 获取基本属性
print('文件是否存在:', file_path.exists()) # True
print('是否为文件:', file_path.is_file()) # True
print('文件大小(字节):', file_path.stat().st_size) # 12(中文字符在UTF-8中占3字节,共4个汉字=12字节)
# 获取时间属性(时间戳需转换为可读格式)
create_time = file_path.stat().st_ctime # 创建时间(Windows)/ 首次修改时间(Unix)
modify_time = file_path.stat().st_mtime # 最后修改时间
print('创建时间:', time.ctime(create_time))
print('最后修改时间:', time.ctime(modify_time))
# 输出示例:
# 文件是否存在: True
# 是否为文件: True
# 文件大小(字节): 12
# 创建时间: Wed Jun 12 10:00:00 2024
# 最后修改时间: Wed Jun 12 10:00:00 2024
七、进阶操作:批量处理与异常处理
实际开发中,文件操作常需处理批量文件和异常情况(如文件不存在、权限不足)。
7.1 批量处理文件(结合 glob 或 pathlib)
示例 7:批量读取目录下所有 .txt 文件
from pathlib import Path
# 获取当前目录下所有 .txt 文件
txt_files = list(Path('.').glob('*.txt')) # 返回 Path 对象列表
print('找到的txt文件:', [f.name for f in txt_files])
# 批量读取每个文件的前50字符
for file in txt_files:
with open(file, 'r', encoding='utf-8', errors='ignore') as f: # errors='ignore' 忽略编码错误
content = f.read(50) # 读取前50字符
print(f'\n{file.name} 内容预览:{content}...')
7.2 异常处理(避免程序崩溃)
文件操作可能出现多种异常(如 FileNotFoundError、PermissionError),需用 try-except 捕获。
示例 8:文件操作异常处理
file_path = 'nonexistent.txt' # 不存在的文件
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except FileNotFoundError:
print(f'错误:文件 {file_path} 不存在')
except PermissionError:
print(f'错误:没有权限访问 {file_path}')
except UnicodeDecodeError:
print(f'错误:{file_path} 编码错误,无法读取')
except Exception as e: # 捕获其他未知异常
print(f'发生未知错误:{e}')
else: # 无异常时执行
print(f'成功读取 {file_path},内容长度:{len(content)}')
finally: # 无论是否有异常都执行(如清理资源)
print('操作结束')
# 输出:
# 错误:文件 nonexistent.txt 不存在
# 操作结束
7.3 大文件处理(避免内存溢出)
处理 GB 级大文件时,不能用 read() 一次性加载,需逐行或分块读取。
示例 9:分块读取大文件(如日志分析)
def process_large_file(file_path, block_size=1024*1024): # 1MB 块
with open(file_path, 'r', encoding='utf-8') as f:
while True:
block = f.read(block_size) # 每次读取1MB
if not block: # 读取完毕
break
# 处理块内容(示例:统计包含"error"的行数)
error_count = block.count('error') + block.count('ERROR')
print(f'当前块错误数:{error_count}')
# 模拟大文件(实际使用时替换为真实大文件路径)
# process_large_file('large_log.txt')
print('大文件处理逻辑:分块读取,逐块处理')
八、常见场景实战
场景 1:CSV 文件读写(模拟数据处理)
# 写入 CSV 数据
with open('data.csv', 'w', encoding='utf-8', newline='') as f: # newline='' 避免多余空行
f.write('姓名,年龄,城市\n')
f.write('张三,25,北京\n')
f.write('李四,30,上海\n')
# 读取并解析 CSV
with open('data.csv', 'r', encoding='utf-8') as f:
header = f.readline().strip().split(',') # 表头
print('表头:', header)
for line in f:
data = line.strip().split(',')
print(f'数据:{data[0]},{data[1]}岁,来自{data[2]}')
# 输出:
# 表头: ['姓名', '年龄', '城市']
# 数据:张三,25岁,来自北京
# 数据:李四,30岁,来自上海
场景 2:日志文件追加(记录程序运行日志)
import datetime
def write_log(message, log_file='app.log'):
"""写入日志,包含时间戳"""
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_line = f'[{timestamp}] {message}\n'
with open(log_file, 'a', encoding='utf-8') as f: # 'a' 追加模式
f.write(log_line)
# 模拟程序运行日志
write_log('程序启动')
write_log('用户登录:admin')
write_log('警告:内存使用率超过80%')
write_log('程序退出')
# 查看日志
with open('app.log', 'r', encoding='utf-8') as f:
print('日志内容:\n', f.read())
输出结果:
日志内容:
[2024-06-12 10:30:00] 程序启动
[2024-06-12 10:30:05] 用户登录:admin
[2024-06-12 10:30:10] 警告:内存使用率超过80%
[2024-06-12 10:30:15] 程序退出
场景 3:文件加密/解密(简单替换加密)
def encrypt_file(src_path, dst_path, key=3):
"""简单凯撒加密:每个字符ASCII码+key"""
with open(src_path, 'r', encoding='utf-8') as src, open(dst_path, 'w', encoding='utf-8') as dst:
for line in src:
encrypted = ''.join([chr(ord(c) + key) for c in line])
dst.write(encrypted)
def decrypt_file(src_path, dst_path, key=3):
"""解密:每个字符ASCII码-key"""
with open(src_path, 'r', encoding='utf-8') as src, open(dst_path, 'w', encoding='utf-8') as dst:
for line in src:
decrypted = ''.join([chr(ord(c) - key) for c in line])
dst.write(decrypted)
# 测试加密解密
with open('secret.txt', 'w', encoding='utf-8') as f:
f.write('Hello, 世界!')
encrypt_file('secret.txt', 'encrypted.txt')
decrypt_file('encrypted.txt', 'decrypted.txt')
# 验证结果
with open('decrypted.txt', 'r', encoding='utf-8') as f:
print('解密后内容:', f.read()) # 输出:Hello, 世界!
九、最佳实践与避坑指南
-
始终用
with语句打开文件:自动管理资源,避免忘记close()导致的文件占用。 -
指定编码格式:文本文件操作时显式指定
encoding='utf-8',避免中文乱码(尤其是 Windows 系统默认 GBK 编码)。 -
区分文本与二进制模式:
- 文本模式(
'r'/'w'):用于.txt、.csv等,读写字符串,需编码; - 二进制模式(
'rb'/'wb'):用于图片、音频、压缩包等,读写字节(bytes),无需编码。
- 文本模式(
-
处理大文件用迭代或分块:避免
read()一次性加载全部内容,防止内存溢出。 -
异常处理不可少:至少捕获
FileNotFoundError(文件不存在)和PermissionError(权限不足)。 -
谨慎使用
'w'模式:会清空原有文件,重要数据操作前先备份。 -
路径处理用
pathlib:跨平台兼容,避免手动拼接路径(如'dir' + '/' + 'file.txt')。
十、总结
Python 文件操作核心围绕 open() 函数和文件对象展开,掌握以下几点即可应对绝大多数场景:
- 基础读写:
read()/write()+with语句; - 模式选择:根据需求选
'r'/'w'/'a'及二进制模式; - 高效处理:大文件用迭代,批量文件用
glob/pathlib; - 安全保障:异常处理 + 显式编码 + 谨慎模式。
通过本文的实例练习,可快速掌握从简单文本读写到复杂批量处理的全流程技能,为数据处理、日志分析、配置管理等任务打下坚实基础。

浙公网安备 33010602011771号