文件操作是编程中绕不开的核心技能------无论是读取配置文件、处理日志数据、存储程序运行结果,还是实现数据的持久化,都离不开对文件的操作。Python 凭借简洁的语法和强大的标准库,让文件处理变得高效而优雅。本文将从基础到高级,系统性地讲解 Python 文件操作的所有知识点,并配有丰富的实战案例。
一、文件操作的核心概念
1.1 文件操作的三部曲
在 Python 中,对文件进行操作的经典流程是三步:打开文件 → 读写文件 → 关闭文件 。打开文件是通过内置的 open() 函数来实现的,它返回一个文件对象。
python
# 传统三步曲(不推荐)
f = open('demo.txt', 'w', encoding='utf-8') # 打开文件
f.write('Hello World') # 读写文件
f.close() # 关闭文件
1.2 为什么必须关闭文件?
手动关闭文件存在两大风险:
- 资源泄漏:未关闭的文件描述符会占用系统资源,可能导致程序运行缓慢甚至崩溃。
- 数据丢失 :写入操作会先进入缓冲区,如果程序异常退出而未调用
close(),缓冲区中的数据可能无法写入磁盘。
推荐使用 with 语句来自动管理文件生命周期。
二、打开文件:open() 函数详解
2.1 基本语法
open() 函数的基本语法如下:
python
file_object = open(file_path, mode='r', encoding=None, buffering=-1)
- file_path :文件路径,支持相对路径(如
data/log.txt)和绝对路径(如C:/project/data.csv)。 - mode:打开模式,决定文件的读写权限。
- encoding :编码格式,处理文本文件时推荐指定为
'utf-8'。
2.2 文件模式详解
open() 函数的第二个参数 mode 决定了可以对文件执行哪些操作。常见的模式如下:
| 模式 | 名称 | 行为 | 适用场景 |
|---|---|---|---|
'r' |
只读 | 文件必须存在,否则报错;指针在开头 | 读取配置/日志 |
'w' |
覆盖写入 | 清空原文件,不存在则创建 | 生成新文件 |
'a' |
追加写入 | 在文件末尾添加内容,不存在则创建 | 持续记录日志 |
'x' |
独占创建 | 如果文件已存在则失败 | 防止意外覆盖 |
'r+' |
读写 | 文件必须存在,可读可写 | 修改文件中间内容 |
'w+' |
读写 | 文件存在则覆盖,不存在则创建 | 写入后读取 |
'a+' |
读写 | 在末尾追加,可读取 | 追加并读取 |
'b' |
二进制模式 | 与 r/w/a 组合使用(如 'rb') |
处理图片/音频等非文本 |
't' |
文本模式 | 默认模式 | 处理文本文件 |
2.3 常用组合模式示例
python
# 以只读模式打开(默认)
with open('config.ini', 'r', encoding='utf-8') as f:
content = f.read()
# 以写入模式打开(覆盖原内容)
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('新内容')
# 以追加模式打开(在末尾添加)
with open('app.log', 'a', encoding='utf-8') as f:
f.write('2026-06-26: 程序启动\n')
# 以二进制模式读取图片
with open('image.jpg', 'rb') as f:
data = f.read()
# 以读写模式打开(文件必须存在)
with open('data.txt', 'r+', encoding='utf-8') as f:
content = f.read()
f.seek(0) # 回到开头
f.write('新内容')
三、文件读写操作
3.1 读取文件的四种方法
文件对象提供了多种读取方法:
| 方法 | 说明 |
|---|---|
read(size=-1) |
读取整个文件或指定大小的数据 |
readline() |
读取一行 |
readlines() |
读取所有行并返回列表 |
| 直接迭代 | 逐行迭代,内存友好 |
(1)read() --- 一次性读取全部内容
适合小文件,大文件会导致内存问题。
python
with open('config.json', 'r', encoding='utf-8') as f:
full_content = f.read()
print(full_content)
# 读取指定字节数
with open('data.txt', 'r', encoding='utf-8') as f:
first_100 = f.read(100) # 读取前100个字符
(2)readline() --- 逐行读取
python
with open('data.txt', 'r', encoding='utf-8') as f:
line1 = f.readline() # 第一行
line2 = f.readline() # 第二行
print(line1, line2)
(3)readlines() --- 读取所有行到列表
python
with open('data.txt', 'r', encoding='utf-8') as f:
lines = f.readlines() # 返回列表,每行作为一个元素
for line in lines:
print(line.strip())
(4)直接迭代 --- 最内存友好的方式
适用于大文件,逐行处理,内存占用恒定。
python
error_count = 0
with open('server.log', 'r', encoding='utf-8') as f:
for line in f: # 逐行读取,内存占用恒定
if 'ERROR' in line:
error_count += 1
print(f"发现 {error_count} 个错误")
3.2 写入文件的两种方法
(1)write() --- 写入单行
python
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('第一行内容\n') # 必须手动添加换行符
f.write('第二行内容\n')
(2)writelines() --- 写入多行
python
lines = ['苹果\n', '香蕉\n', '橙子\n']
with open('fruits.txt', 'w', encoding='utf-8') as f:
f.writelines(lines) # 不会自动添加换行符,需要自行处理
# 如果列表中元素没有换行符,可以这样处理
data = ['苹果', '香蕉', '橙子']
clean_data = [f"{item}\n" for item in data]
with open('fruits.txt', 'w', encoding='utf-8') as f:
f.writelines(clean_data)
四、with 语句与上下文管理器
4.1 为什么使用 with 语句?
传统的 open() + close() 模式存在三大隐患:
- 忘记调用
close()导致资源泄漏 - 异常发生时文件无法正常关闭
- 代码冗余
with 语句通过上下文管理器自动处理资源释放,即使发生异常也能确保文件关闭。
4.2 基本用法
python
# 传统方式(存在风险)
try:
f = open('data.txt', 'r')
data = f.read()
finally:
f.close() # 必须手动关闭
# with 语句(优雅且安全)
with open('data.txt', 'r', encoding='utf-8') as f:
data = f.read()
# 离开 with 块后文件自动关闭,无需手动操作
4.3 同时打开多个文件
python
# 同时读写两个文件
with open('source.txt', 'r', encoding='utf-8') as src, \
open('dest.txt', 'w', encoding='utf-8') as dst:
content = src.read()
dst.write(content)
五、文件指针控制:seek() 与 tell()
文件指针记录了当前读写位置,通过 seek() 和 tell() 可以实现精确定位控制。
5.1 tell() --- 获取当前位置
python
with open('data.txt', 'r', encoding='utf-8') as f:
print(f.tell()) # 0,指针在开头
f.read(10)
print(f.tell()) # 10,已读取10个字节
5.2 seek() --- 移动指针
seek(offset, whence) 的语法:
- offset:偏移量(字节数)
- whence :参照位置
0(默认):文件开头1:当前位置2:文件末尾
python
with open('data.txt', 'rb') as f:
# 回到文件开头
f.seek(0)
# 移动到文件末尾前10个字节
f.seek(-10, 2)
last_10_bytes = f.read()
# 从当前位置向后移动5个字节
f.seek(5, 1)
5.3 实战:读取文件首尾
python
# 读取文件前10字节和最后10字节
with open('large_file.dat', 'rb') as f:
first_part = f.read(10) # 读取前10字节
f.seek(-10, 2) # 移动到末尾前10字节
last_part = f.read(10) # 读取最后10字节
六、异常处理
文件操作中可能会遇到各种异常,如文件不存在、权限不足等。使用 try-except 可以优雅地处理这些情况。
6.1 常见异常类型
FileNotFoundError:文件不存在PermissionError:权限不足IOError:其他 I/O 错误
6.2 异常处理示例
python
try:
with open('config.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
except FileNotFoundError:
print('错误:文件不存在')
except PermissionError:
print('错误:没有读取权限')
except IOError as e:
print(f'错误:无法读取文件 - {e}')
6.3 使用 try-except-finally
python
f = None
try:
f = open('data.txt', 'r', encoding='utf-8')
content = f.read()
print(content)
except FileNotFoundError:
print('文件不存在')
finally:
if f:
f.close() # 确保文件被关闭
print('操作完成')
七、文件编码处理
7.1 编码的基本概念
Python 3 中,文本文件默认使用 UTF-8 编码。但实际工作中可能会遇到各种编码的文件(如 GBK、GB2312 等),需要正确处理以避免乱码。
7.2 指定编码读写文件
python
# 读取 UTF-8 编码的文件
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 读取 GBK 编码的文件
with open('data_gbk.txt', 'r', encoding='gbk') as f:
content = f.read()
# 写入时指定编码
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('你好,世界!')
7.3 处理编码错误
python
# 忽略无法解码的字符
with open('data.txt', 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 用替代字符替换无法解码的字符
with open('data.txt', 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
7.4 自动检测文件编码
可以使用 chardet 库来检测文件编码:
python
import chardet
with open('unknown.txt', 'rb') as f:
raw_data = f.read()
result = chardet.detect(raw_data)
encoding = result['encoding']
print(f'检测到的编码:{encoding}')
with open('unknown.txt', 'r', encoding=encoding) as f:
content = f.read()
八、二进制文件操作
对于非文本文件(如图片、音频、视频等),需要使用二进制模式。
8.1 读取二进制文件
python
# 读取图片文件
with open('image.jpg', 'rb') as f:
image_data = f.read()
print(f'文件大小:{len(image_data)} 字节')
8.2 写入二进制文件
python
# 复制二进制文件
with open('source.jpg', 'rb') as src:
data = src.read()
with open('copy.jpg', 'wb') as dst:
dst.write(data)
8.3 大文件分块复制
对于大文件,应该分块读取和写入以避免内存占用过高:
python
chunk_size = 4096 # 4KB 块大小
with open('source.mp4', 'rb') as src:
with open('copy.mp4', 'wb') as dst:
while True:
chunk = src.read(chunk_size)
if not chunk:
break
dst.write(chunk)
九、os 模块:文件和目录操作
os 模块提供了与操作系统交互的功能,包括文件和目录操作。
9.1 常用 os 文件操作
python
import os
# 获取当前工作目录
current_dir = os.getcwd()
print(f'当前目录:{current_dir}')
# 列出目录下的所有文件和子目录
files = os.listdir('.')
print(f'目录内容:{files}')
# 重命名文件
os.rename('old_name.txt', 'new_name.txt')
# 删除文件
os.remove('unnecessary.txt')
# 检查文件是否存在
if os.path.exists('data.txt'):
print('文件存在')
# 检查是否为文件
if os.path.isfile('data.txt'):
print('这是一个文件')
# 检查是否为目录
if os.path.isdir('my_folder'):
print('这是一个目录')
9.2 目录操作
python
import os
# 创建单级目录
os.mkdir('new_folder')
# 创建多级目录
os.makedirs('parent/child/grandchild')
# 删除空目录
os.rmdir('empty_folder')
# 删除目录树(谨慎使用)
import shutil
shutil.rmtree('folder_to_delete')
9.3 路径操作
python
import os
# 拼接路径
path = os.path.join('folder', 'subfolder', 'file.txt')
print(path) # folder/subfolder/file.txt (Windows 下为 folder\subfolder\file.txt)
# 获取文件名
filename = os.path.basename('/path/to/file.txt') # file.txt
# 获取目录名
dirname = os.path.dirname('/path/to/file.txt') # /path/to
# 分离文件名和扩展名
name, ext = os.path.splitext('file.txt') # ('file', '.txt')
十、shutil 模块:高级文件操作
shutil 模块提供了更高级的文件操作功能,如复制、移动、删除目录树等。
10.1 复制文件
python
import shutil
# 复制文件(复制内容和权限)
shutil.copy('source.txt', 'destination.txt')
# 复制文件(保留元数据)
shutil.copy2('source.txt', 'destination.txt')
10.2 复制目录
python
import shutil
# 递归复制整个目录
shutil.copytree('source_folder', 'destination_folder')
10.3 移动文件/目录
python
import shutil
# 移动文件或目录
shutil.move('source.txt', 'new_location/source.txt')
10.4 删除目录树
python
import shutil
# 递归删除整个目录(谨慎使用!)
shutil.rmtree('folder_to_delete')
十一、CSV 文件读写
CSV(逗号分隔值)是存储表格数据的常用格式。Python 内置的 csv 模块提供了便捷的读写功能。
11.1 写入 CSV 文件
python
import csv
# 写入 CSV(使用 writer)
data = [
['姓名', '年龄', '城市'],
['张三', 25, '北京'],
['李四', 30, '上海'],
['王五', 28, '深圳']
]
with open('people.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(data)
# 使用 DictWriter 写入字典数据
data_dict = [
{'name': '张三', 'age': 25, 'city': '北京'},
{'name': '李四', 'age': 30, 'city': '上海'}
]
with open('people_dict.csv', 'w', newline='', encoding='utf-8') as f:
fieldnames = ['name', 'age', 'city']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(data_dict)
11.2 读取 CSV 文件
python
import csv
# 读取 CSV(使用 reader)
with open('people.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row) # row 是一个列表
# 使用 DictReader 读取为字典
with open('people_dict.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(row['name'], row['age'], row['city'])
十二、JSON 文件读写
JSON 是轻量级的数据交换格式,与 Python 的字典和列表天然契合。
12.1 写入 JSON 文件
python
import json
data = {
'name': '张三',
'age': 25,
'city': '北京',
'hobbies': ['阅读', '编程', '旅游']
}
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4) # indent 美化输出
12.2 读取 JSON 文件
python
import json
with open('data.json', 'r', encoding='utf-8') as f:
data = json.load(f)
print(data['name']) # 张三
print(data['hobbies']) # ['阅读', '编程', '旅游']
12.3 JSON 与字符串互转
python
import json
# Python 对象 → JSON 字符串
data = {'name': '张三', 'age': 25}
json_str = json.dumps(data, ensure_ascii=False)
print(json_str) # {"name": "张三", "age": 25}
# JSON 字符串 → Python 对象
json_str = '{"name": "张三", "age": 25}'
data = json.loads(json_str)
print(data['name']) # 张三
十三、临时文件与目录
tempfile 模块用于创建临时文件和目录,在程序结束后会自动清理。
13.1 创建临时文件
python
import tempfile
# 创建临时文件(匿名,不保留文件名)
with tempfile.TemporaryFile(mode='w+t') as tf:
tf.write('临时数据')
tf.seek(0)
content = tf.read()
print(content)
# 离开 with 块后文件自动删除
# 创建有名称的临时文件
with tempfile.NamedTemporaryFile(mode='w+t', delete=True) as tf:
tf.write('临时数据')
print(f'临时文件路径:{tf.name}')
tf.seek(0)
print(tf.read())
# 离开 with 块后文件自动删除
13.2 创建临时目录
python
import tempfile
import os
# 创建临时目录
with tempfile.TemporaryDirectory() as tmpdir:
print(f'临时目录:{tmpdir}')
# 在临时目录中创建文件
with open(os.path.join(tmpdir, 'temp.txt'), 'w') as f:
f.write('临时文件')
# 离开 with 块后目录自动删除
十四、综合实战案例
14.1 案例一:日志分析器
统计日志文件中的错误数量,并将错误行写入单独的文件。
python
def analyze_log(log_file, error_output):
"""分析日志文件,提取错误行"""
error_count = 0
error_lines = []
try:
with open(log_file, 'r', encoding='utf-8') as f:
for line in f:
if 'ERROR' in line.upper():
error_count += 1
error_lines.append(line.strip())
# 将错误行写入输出文件
with open(error_output, 'w', encoding='utf-8') as f:
f.write(f'共发现 {error_count} 个错误:\n')
f.write('=' * 50 + '\n')
f.writelines([line + '\n' for line in error_lines])
print(f'分析完成!共发现 {error_count} 个错误')
print(f'错误详情已保存到:{error_output}')
except FileNotFoundError:
print(f'错误:日志文件 {log_file} 不存在')
except PermissionError:
print(f'错误:没有权限读取文件 {log_file}')
except IOError as e:
print(f'错误:{e}')
# 使用示例
analyze_log('server.log', 'error_report.txt')
14.2 案例二:学生成绩管理系统(数据持久化)
将学生数据保存到 JSON 文件,实现数据的持久化存储。
python
import json
import os
class StudentManager:
def __init__(self, data_file='students.json'):
self.data_file = data_file
self.students = []
self.load_data()
def load_data(self):
"""从文件加载学生数据"""
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
self.students = json.load(f)
print(f'已加载 {len(self.students)} 名学生数据')
except (json.JSONDecodeError, IOError) as e:
print(f'加载数据失败:{e}')
self.students = []
else:
print('数据文件不存在,将创建新文件')
def save_data(self):
"""保存学生数据到文件"""
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(self.students, f, ensure_ascii=False, indent=2)
print('数据已保存')
except IOError as e:
print(f'保存数据失败:{e}')
def add_student(self, name, age, score):
"""添加学生"""
student = {'name': name, 'age': age, 'score': score}
self.students.append(student)
self.save_data()
def list_students(self):
"""列出所有学生"""
if not self.students:
print('暂无学生数据')
return
print('=' * 40)
print(f"{'姓名':<10} {'年龄':<6} {'成绩':<6}")
print('-' * 40)
for s in self.students:
print(f"{s['name']:<10} {s['age']:<6} {s['score']:<6}")
print('=' * 40)
def get_average_score(self):
"""计算平均成绩"""
if not self.students:
return 0
total = sum(s['score'] for s in self.students)
return total / len(self.students)
# 使用示例
manager = StudentManager('students.json')
# 添加学生
manager.add_student('张三', 18, 92)
manager.add_student('李四', 19, 85)
manager.add_student('王五', 18, 78)
# 列出所有学生
manager.list_students()
# 计算平均成绩
print(f'平均成绩:{manager.get_average_score():.2f}')
14.3 案例三:大文件处理
处理大型日志文件时,使用分块读取和逐行处理来节约内存。
python
def process_large_file(file_path, keyword, output_file):
"""
在大文件中搜索包含关键字的行
使用逐行读取,内存友好
"""
found_count = 0
try:
with open(file_path, 'r', encoding='utf-8') as src:
with open(output_file, 'w', encoding='utf-8') as dst:
for line in src:
if keyword in line:
dst.write(line)
found_count += 1
# 每处理1000行显示一次进度
if found_count % 1000 == 0:
print(f'已找到 {found_count} 行')
print(f'处理完成!共找到 {found_count} 行包含 "{keyword}" 的内容')
print(f'结果已保存到:{output_file}')
except FileNotFoundError:
print(f'错误:文件 {file_path} 不存在')
except MemoryError:
print('错误:内存不足,请尝试使用分块处理')
except Exception as e:
print(f'处理过程中发生错误:{e}')
# 使用示例
process_large_file('huge_server.log', 'ERROR', 'errors.txt')
14.4 案例四:文件批量重命名
批量重命名指定目录下的所有文件,添加日期前缀。
python
import os
from datetime import datetime
def batch_rename(directory, prefix=None):
"""
批量重命名目录下的所有文件
添加日期前缀或自定义前缀
"""
if not os.path.exists(directory):
print(f'错误:目录 {directory} 不存在')
return
if not os.path.isdir(directory):
print(f'错误:{directory} 不是一个目录')
return
# 获取所有文件
files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
if not files:
print('目录中没有文件')
return
# 生成前缀
if prefix is None:
prefix = datetime.now().strftime('%Y%m%d_')
renamed_count = 0
for filename in files:
# 跳过已经包含前缀的文件
if filename.startswith(prefix):
continue
old_path = os.path.join(directory, filename)
new_path = os.path.join(directory, prefix + filename)
try:
os.rename(old_path, new_path)
renamed_count += 1
print(f'重命名:{filename} → {prefix + filename}')
except OSError as e:
print(f'重命名 {filename} 失败:{e}')
print(f'完成!共重命名了 {renamed_count} 个文件')
# 使用示例
batch_rename('./documents', '20260626_')
十五、最佳实践总结
15.1 核心原则
- 始终使用
with语句管理文件,自动处理资源的打开和关闭。 - 始终指定编码 (如
encoding='utf-8'),避免乱码问题。 - 处理大文件时使用逐行读取或分块读取,避免内存溢出。
- 做好异常处理 ,捕获
FileNotFoundError、PermissionError等常见异常。 - 处理文件路径时使用
os.path.join(),保证跨平台兼容性。
15.2 常见陷阱与解决方案
| 陷阱 | 解决方案 |
|---|---|
| 忘记关闭文件导致资源泄漏 | 使用 with 语句自动管理 |
| 写入内容未保存 | 调用 flush() 或使用 with 语句 |
| 中文乱码 | 读写时明确指定 encoding='utf-8' |
| 大文件一次性读取导致内存爆炸 | 使用逐行读取或分块读取 |
| 文件路径在不同操作系统不兼容 | 使用 os.path.join() 拼接路径 |
15.3 速查表
| 操作 | 代码 |
|---|---|
| 打开文件 | with open(path, mode, encoding=enc) as f: |
| 读取全部 | content = f.read() |
| 读取一行 | line = f.readline() |
| 读取所有行 | lines = f.readlines() |
| 逐行迭代 | for line in f: |
| 写入字符串 | f.write(text) |
| 写入多行 | f.writelines(list_of_strings) |
| 获取指针位置 | pos = f.tell() |
| 移动指针 | f.seek(offset, whence) |
| 复制文件 | shutil.copy(src, dst) |
| 移动文件 | shutil.move(src, dst) |
| 删除文件 | os.remove(path) |
| 检查文件存在 | os.path.exists(path) |
| 读取 CSV | csv.reader(f) |
| 写入 CSV | csv.writer(f) |
| 读取 JSON | json.load(f) |
| 写入 JSON | json.dump(obj, f) |
通过本文的系统学习,你应该已经掌握了 Python 文件操作的所有核心知识点------从基础的打开、读写、关闭,到进阶的指针控制、异常处理、编码管理,再到 os/shutil 模块、CSV/JSON 格式处理,以及大文件处理和临时文件等高级技巧。熟练运用这些技能,你将能够高效地处理各种文件操作场景,写出更加健壮和专业的 Python 程序。