【第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)可省略,默认缩写为核心操作符(如 rtrwt+w+)。
  • 二进制模式(b)必须显式写出,无缩写(如 rbab+ 需完整写)。

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

六、文件属性与元数据

通过 ospathlib 库可获取文件的元数据(大小、创建时间、修改时间等)。

示例 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 批量处理文件(结合 globpathlib

示例 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 异常处理(避免程序崩溃)

文件操作可能出现多种异常(如 FileNotFoundErrorPermissionError),需用 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, 世界!

九、最佳实践与避坑指南

  1. 始终用 with 语句打开文件:自动管理资源,避免忘记 close() 导致的文件占用。

  2. 指定编码格式:文本文件操作时显式指定 encoding='utf-8',避免中文乱码(尤其是 Windows 系统默认 GBK 编码)。

  3. 区分文本与二进制模式

    • 文本模式('r'/'w'):用于 .txt.csv 等,读写字符串,需编码;
    • 二进制模式('rb'/'wb'):用于图片、音频、压缩包等,读写字节(bytes),无需编码。
  4. 处理大文件用迭代或分块:避免 read() 一次性加载全部内容,防止内存溢出。

  5. 异常处理不可少:至少捕获 FileNotFoundError(文件不存在)和 PermissionError(权限不足)。

  6. 谨慎使用 'w' 模式:会清空原有文件,重要数据操作前先备份。

  7. 路径处理用 pathlib:跨平台兼容,避免手动拼接路径(如 'dir' + '/' + 'file.txt')。

十、总结

Python 文件操作核心围绕 open() 函数和文件对象展开,掌握以下几点即可应对绝大多数场景:

  • 基础读写:read()/write() + with 语句;
  • 模式选择:根据需求选 'r'/'w'/'a' 及二进制模式;
  • 高效处理:大文件用迭代,批量文件用 glob/pathlib
  • 安全保障:异常处理 + 显式编码 + 谨慎模式。

通过本文的实例练习,可快速掌握从简单文本读写到复杂批量处理的全流程技能,为数据处理、日志分析、配置管理等任务打下坚实基础。

posted @ 2025-11-15 22:17  wangya216  阅读(243)  评论(0)    收藏  举报