Python文件读写方法详解
1. 引言
文件操作是编程中的基础技能,也是Python应用开发中不可或缺的一部分。无论是处理配置文件、日志记录、数据持久化,还是读写用户数据,都离不开文件操作。Python提供了强大而灵活的文件处理功能,通过内置函数和模块可以轻松实现文件的创建、读取、写入、删除等操作。本教程将系统性地介绍Python中文件读写的各种方法,从基础操作到高级技巧,帮助读者全面掌握Python文件处理技术。
2. 文件操作基础
在Python中,文件操作主要涉及三个核心步骤:打开文件、操作文件内容、关闭文件。Python使用内置的open()函数来打开文件,该函数返回一个文件对象,通过该对象可以执行各种文件操作。
2.1. 文件打开模式
open()函数的基本语法为:open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)。其中最关键的参数是mode,它指定了文件的打开模式:
'r':只读模式(默认)
'w':写入模式(会覆盖已存在的文件)
'a':追加模式(在文件末尾添加内容)
'x':独占创建模式(如果文件已存在则失败)
'b':二进制模式(与其他模式组合使用)
't':文本模式(默认,与其他模式组合使用)
'+':读写模式(与其他模式组合使用)
常见的模式组合包括:
# 只读文本模式(默认)
open('file.txt', 'r')
# 只写文本模式(覆盖)
open('file.txt', 'w')
# 追加文本模式
open('file.txt', 'a')
# 读写文本模式
open('file.txt', 'r+')
# 只读二进制模式
open('file.bin', 'rb')
# 只写二进制模式(覆盖)
open('file.bin', 'wb')
# 读写二进制模式
open('file.bin', 'rb+')
2.2. 文件对象基本操作
文件对象提供了多种方法来操作文件内容:
read(size=-1):读取文件内容,size指定读取的字符(或字节)数
readline(size=-1):读取一行内容
readlines(hint=-1):读取所有行并返回列表
write(s):将字符串s写入文件
writelines(lines):将字符串列表写入文件
seek(offset, whence=0):移动文件指针位置
tell():返回当前文件指针位置
close():关闭文件
3. 文本文件读写
3.1. 读取文本文件
文本文件是最常见的文件类型,Python提供了多种方法来读取文本文件内容。以下是几种常用的读取方法:
# 方法1:使用read()读取整个文件
with open('example.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
# 方法2:使用readline()逐行读取
with open('example.txt', 'r', encoding='utf-8') as f:
line = f.readline()
while line:
print(line, end='')
line = f.readline()
# 方法3:使用readlines()读取所有行到列表
with open('example.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
print(line, end='')
# 方法4:直接迭代文件对象(推荐)
with open('example.txt', 'r', encoding='utf-8') as f:
for line in f:
print(line, end='')
代码说明:
使用with语句可以确保文件在使用后自动关闭,即使发生异常也能正确关闭文件
encoding参数指定文件的编码方式,处理非ASCII字符时必须指定(如中文常用'utf-8')
readline()会保留每行末尾的换行符,使用print(line, end='')可以避免重复换行
直接迭代文件对象是Pythonic的方式,既简洁又高效,特别是处理大文件时
3.2. 写入文本文件
写入文本文件同样有多种方法,根据需求可以选择覆盖写入或追加写入:
# 覆盖写入文件
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('这是第一行\n')
f.write('这是第二行\n')
f.write('这是第三行\n')
# 追加写入文件
with open('output.txt', 'a', encoding='utf-8') as f:
f.write('这是追加的第一行\n')
f.write('这是追加的第二行\n')
# 写入多行数据
lines = ['第一行\n', '第二行\n', '第三行\n']
with open('output.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)
代码说明:
'w'模式会覆盖原有文件内容,如果文件不存在则创建新文件
'a'模式会在文件末尾追加内容,不会覆盖原有内容
writelines()方法可以写入一个字符串列表,但不会自动添加换行符,所以每行需要自己包含换行符
写入文本时,如果字符串中包含特殊字符,确保编码方式正确
4. 二进制文件读写
二进制文件用于处理非文本数据,如图片、音频、视频、可执行文件等。处理二进制文件时,需要在打开模式中添加'b'标志。
4.1. 读取二进制文件
# 读取二进制文件(如图片)
with open('image.jpg', 'rb') as f:
data = f.read()
print(f'文件大小: {len(data)} 字节')
# 分块读取大文件
chunk_size = 4096
with open('large_file.zip', 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
# 处理每个数据块
print(f'读取块大小: {len(chunk)} 字节')
代码说明:
二进制模式使用'rb'打开文件,返回的是字节对象而非字符串
对于大文件,应该分块读取,避免内存不足
读取到的二进制数据可以按字节处理,也可以使用struct模块解析二进制数据
4.2. 写入二进制文件
# 写入二进制数据
data = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09'
with open('binary.bin', 'wb') as f:
f.write(data)
# 复制文件(二进制方式)
def copy_binary(src, dst):
with open(src, 'rb') as fsrc:
with open(dst, 'wb') as fdst:
while True:
chunk = fsrc.read(4096)
if not chunk:
break
fdst.write(chunk)
# 使用示例
copy_binary('source.jpg', 'destination.jpg')
代码说明:
二进制写入使用'wb'模式,写入的数据必须是字节对象(以b前缀或bytes()创建)
复制文件时使用二进制模式可以保证文件的完整性,特别是对于非文本文件
分块写入同样适用于大文件处理
5. 文件和目录操作
除了基本的读写操作,Python还提供了os和shutil模块用于处理文件和目录操作。
5.1. 基本文件和目录操作
import os
import shutil
# 检查文件是否存在
if os.path.exists('example.txt'):
print('文件存在')
else:
print('文件不存在')
# 获取文件信息
if os.path.exists('example.txt'):
file_size = os.path.getsize('example.txt') # 字节大小
file_mtime = os.path.getmtime('example.txt') # 修改时间(时间戳)
print(f'文件大小: {file_size} 字节')
print(f'修改时间: {file_mtime}')
# 创建目录
os.makedirs('data/files', exist_ok=True) # exist_exist=True避免目录已存在时报错
# 列出目录内容
for item in os.listdir('.'):
print(item)
# 遍历目录树
for root, dirs, files in os.walk('.'):
print(f'当前目录: {root}')
for dir in dirs:
print(f'子目录: {dir}')
for file in files:
print(f'文件: {file}')
# 删除文件
if os.path.exists('temp.txt'):
os.remove('temp.txt')
# 删除目录(必须为空)
if os.path.exists('empty_dir'):
os.rmdir('empty_dir')
# 删除目录及其内容(谨慎使用!)
if os.path.exists('data'):
shutil.rmtree('data')
# 重命名文件或目录
os.rename('old_name.txt', 'new_name.txt')
代码说明:
os.path模块提供了许多路径处理函数,如exists()、getsize()、getmtime()等
os.makedirs()可以创建多级目录,exist_ok=True参数可以防止目录已存在时报错
os.walk()是遍历目录树的强大工具,返回三元组(root, dirs, files)
删除文件和目录需要谨慎,特别是shutil.rmtree()会递归删除目录及其所有内容
os.rename()可以重命名文件或目录,也可以用于移动文件
5.2. 路径处理
import os
# 构建路径(跨平台兼容)
file_path = os.path.join('folder', 'subfolder', 'file.txt')
print(file_path) # 在Unix-like系统显示: folder/subfolder/file.txt
# 获取绝对路径
abs_path = os.path.abspath('example.txt')
print(f'绝对路径: {abs_path}')
# 获取路径的目录名和文件名
dirname = os.path.dirname(abs_path)
filename = os.path.basename(abs_path)
print(f'目录: {dirname}')
print(f'文件名: {filename}')
# 分割路径的扩展名
root, ext = os.path.splitext(filename)
print(f'主文件名: {root}')
print(f'扩展名: {ext}')
# 检查路径类型
if os.path.isfile(abs_path):
print('这是一个文件')
elif os.path.isdir(abs_path):
print('这是一个目录')
# 获取当前工作目录
cwd = os.getcwd()
print(f'当前工作目录: {cwd}')
# 改变当前工作目录
os.chdir('..')
print(f'更改后的工作目录: {os.getcwd()}')
代码说明:
os.path.join()会自动根据操作系统使用正确的路径分隔符(Windows用'\',Unix用'/')
os.path.abspath()获取文件的绝对路径
os.path.dirname()和os.path.basename()分别获取路径的目录部分和文件名部分
os.path.splitext()分割文件名和扩展名,返回元组(root, ext)
os.path.isfile()和os.path.isdir()用于判断路径类型
os.getcwd()获取当前工作目录,os.chdir()改变当前工作目录
6. 上下文管理器(with语句)
在文件操作中,使用with语句是一个非常好的实践,它可以确保文件在使用后自动关闭,即使在处理文件时发生异常。
6.1. 基本用法
# 使用with语句自动管理文件资源
with open('example.txt', 'r') as f:
content = f.read()
# 在with块内操作文件
print(content)
# 文件会自动关闭,即使发生异常
# 等价于传统写法(不推荐)
f = open('example.txt', 'r')
try:
content = f.read()
print(content)
finally:
f.close() # 确保文件关闭
代码说明:
with语句会在代码块执行完毕后自动调用文件对象的close()方法
即使在代码块中发生异常,with语句也能保证文件被正确关闭
使用with语句可以避免资源泄漏,是Python中资源管理的推荐方式
6.2. 自定义上下文管理器
除了文件对象,我们也可以创建自己的上下文管理器,通过实现__enter__和__exit__方法:
class ManagedFile:
def __init__(self, name, mode):
self.name = name
self.mode = mode
def __enter__(self):
self.file = open(self.name, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# 如果返回True,则异常被处理;返回False或None,则异常继续传播
return False
# 使用自定义上下文管理器
with ManagedFile('example.txt', 'r') as f:
content = f.read()
print(content)
代码说明:
__enter__方法在进入with块时调用,返回的对象会赋值给as后的变量
__exit__方法在退出with块时调用,可以处理异常和资源释放
__exit__方法的参数包含异常信息,如果返回True,则异常被抑制;返回False或None,异常继续传播
7. 文件指针和定位
文件操作中,文件指针(或称文件位置指示器)表示当前读写操作在文件中的位置。Python提供了seek()和tell()方法来控制文件指针。
7.1. 文件指针操作
# 写入文件
with open('pointer_demo.txt', 'w') as f:
f.write('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
# 读取并操作文件指针
with open('pointer_demo.txt', 'r+') as f:
# 读取前5个字符
print(f.read(5)) # 输出: 01234
# 获取当前指针位置
pos = f.tell()
print(f'当前位置: {pos}') # 输出: 当前位置: 5
# 移动指针到文件开头
f.seek(0)
print(f.read(5)) # 输出: 01234
# 移动指针到第10个字符
f.seek(10)
print(f.read(5)) # 输出: ABCDE
# 移动指针到文件末尾
f.seek(0, 2) # 2表示从文件末尾开始
print(f'文件末尾位置: {f.tell()}') # 输出: 文件末尾位置: 36
# 从当前位置移动指针
f.seek(-5, 1) # 1表示从当前位置开始
print(f.read()) # 输出: VWXYZ
代码说明:
tell()方法返回当前文件指针的位置(字节偏移量)
seek(offset, whence)方法移动文件指针:
whence=0(默认):从文件开头计算偏移量
whence=1:从当前位置计算偏移量
whence=2:从文件末尾计算偏移量
在文本模式下,seek()只能移动到文件开头或相对于当前位置的位置,而不能移动到文件末尾(某些实现可能支持)
在二进制模式下,seek()可以移动到文件的任何位置
7.2. 文件指针限制
在文本文件中,文件指针的使用有一些限制:
# 创建文本文件包含多字节字符(中文)
with open('chinese.txt', 'w', encoding='utf-8') as f:
f.write('你好,世界!') # 6个中文字符,UTF-8编码占18字节
# 尝试随机访问
with open('chinese.txt', 'r', encoding='utf-8') as f:
# 读取前3个字符
print(f.read(3)) # 输出: 你好,
# 获取当前指针位置
pos = f.tell()
print(f'当前位置: {pos}') # 输出: 当前位置: 9 (3个UTF-8字符占9字节)
# 尝试移动指针到中间位置(可能会导致错误)
try:
f.seek(5) # 移动到第5个字节(在字符中间)
print(f.read(1)) # 可能会抛出错误或输出乱码
except UnicodeDecodeError:
print('错误:无法在多字节字符中间定位')
代码说明:
在UTF-8编码中,一个中文字符通常占用3个字节
在文本模式下,seek()只能移动到字符的边界位置,否则会导致解码错误
对于包含多字节字符的文本文件,随机访问需要特别小心,最好按字符而非字节操作
8. 文件读取的高效方法
处理大文件时,需要特别注意内存使用情况。Python提供了多种高效读取文件的方法,特别是处理大文件时。
8.1. 逐行读取大文件
# 高效读取大文件(逐行处理)
def process_large_file(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
# 逐行处理,每次只加载一行到内存
process_line(line)
def process_line(line):
# 示例:计算每行的字符数
print(f'行长度: {len(line.rstrip())}')
# 使用示例
process_large_file('large_file.log')
代码说明:
直接迭代文件对象是处理大文件的最高效方式,因为它一次只加载一行到内存
这种方法适用于任何大小的文件,因为内存使用量与文件大小无关
使用rstrip()可以去除每行末尾的换行符
8.2. 生成器读取文件
对于更复杂的文件处理,可以使用生成器来逐步处理文件内容:
# 使用生成器读取文件
def read_in_chunks(file_path, chunk_size=4096):
"""生成器函数:分块读取文件"""
with open(file_path, 'r', encoding='utf-8') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
# 使用生成器处理文件
for chunk in read_in_chunks('large_file.txt'):
# 处理每个数据块
print(f'处理块大小: {len(chunk)} 字符')
# 这里可以添加实际处理逻辑
代码说明:
生成器函数read_in_chunks每次只读取指定大小的数据块
使用yield关键字返回数据块,而不是一次性加载整个文件
这种方法适用于需要按固定大小处理文件内容的场景
8.3. 内存映射文件
对于非常大的二进制文件,可以使用mmap模块进行内存映射,将文件直接映射到内存中:
import mmap
# 使用mmap读取大文件
def read_with_mmap(file_path):
with open(file_path, 'r+b') as f:
# 内存映射文件
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
# 像操作内存一样操作文件
print(f'文件大小: {len(mm)} 字节')
# 读取前100个字节
print(mm[:100])
# 搜索数据
index = mm.find(b'pattern')
if index != -1:
print(f'找到匹配位置: {index}')
# 使用示例
read_with_mmap('large_file.bin')
代码说明:
mmap模块将文件直接映射到内存,可以像操作内存一样操作文件内容
这种方法适用于非常大的二进制文件,特别是需要随机访问的场景
ACCESS_READ表示只读模式,也可以使用ACCESS_WRITE或ACCESS_COPY模式
内存映射文件可以显著提高大文件的访问速度
9. 异常处理
文件操作中可能会遇到各种异常情况,如文件不存在、权限不足、磁盘已满等。良好的异常处理可以使程序更加健壮。
9.1. 常见文件异常
Python中与文件操作相关的异常类型:
FileNotFoundError:文件不存在
PermissionError:权限不足
IsADirectoryError:对目录执行文件操作
NotADirectoryError:对文件执行目录操作
FileExistsError:文件已存在(在独占创建模式下)
IOError:输入/输出错误(在Python 3.3+中,它是OSError的别名)
OSError:操作系统相关错误
9.2. 异常处理示例
# 完整的文件操作异常处理
def safe_read_file(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return content
except FileNotFoundError:
print(f'错误:文件 {file_path} 不存在')
return None
except PermissionError:
print(f'错误:没有权限读取文件 {file_path}')
return None
except IsADirectoryError:
print(f'错误:{file_path} 是一个目录,不是文件')
return None
except UnicodeDecodeError:
print(f'错误:文件 {file_path} 编码不正确')
return None
except OSError as e:
print(f'操作系统错误: {e}')
return None
except Exception as e:
print(f'未知错误: {e}')
return None
# 使用示例
content = safe_read_file('example.txt')
if content is not None:
print('文件读取成功')
# 写入文件异常处理
def safe_write_file(file_path, content):
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f'成功写入文件 {file_path}')
return True
except PermissionError:
print(f'错误:没有权限写入文件 {file_path}')
return False
except FileExistsError:
print(f'错误:文件 {file_path} 已存在(独占模式)')
return False
except OSError as e:
print(f'操作系统错误: {e}')
return False
except Exception as e:
print(f'未知错误: {e}')
return False
# 使用示例
safe_write_file('output.txt', '这是一个测试文件')
代码说明:
在文件操作中,应该捕获可能发生的异常,并提供适当的错误处理
常见的异常包括文件不存在、权限问题、编码错误等
使用多个except块可以处理不同类型的异常
最后一个except Exception可以捕获所有未处理的异常
在异常处理中,应该提供有用的错误信息,并确保资源被正确释放
9.3. 使用try-except-finally
# 使用try-except-finally确保资源释放
file = None
try:
file = open('example.txt', 'r')
content = file.read()
print(content)
except FileNotFoundError:
print('文件不存在')
finally:
if file is not None:
file.close() # 确保文件关闭
代码说明:
finally块中的代码无论是否发生异常都会执行
在finally块中关闭文件可以确保资源被释放
这种方法不如with语句简洁,但在某些复杂场景下仍然有用
10. 实战案例:日志文件分析器
下面是一个综合实例,展示如何读取日志文件,分析其中的数据,并将结果写入新文件:
import os
from collections import Counter
def analyze_log_file(log_file_path, output_file_path):
"""
分析日志文件并生成报告
参数:
log_file_path: 日志文件路径
output_file_path: 输出报告文件路径
"""
# 检查输入文件是否存在
if not os.path.exists(log_file_path):
print(f'错误:日志文件 {log_file_path} 不存在')
return
# 初始化统计信息
total_lines = 0
error_count = 0
warning_count = 0
ip_counter = Counter()
# 读取日志文件
try:
with open(log_file_path, 'r', encoding='utf-8') as log_file:
for line in log_file:
total_lines += 1
# 简单的日志分析(假设每行包含IP地址和日志级别)
parts = line.strip().split()
if len(parts) >= 2:
ip = parts[0]
level = parts[1].upper()
# 统计IP地址
ip_counter[ip] += 1
# 统计错误和警告
if level == 'ERROR':
error_count += 1
elif level == 'WARNING':
warning_count += 1
except Exception as e:
print(f'读取日志文件时出错: {e}')
return
# 生成报告
report = []
report.append('日志分析报告')
report.append('=' * 40)
report.append(f'总行数: {total_lines}')
report.append(f'错误数: {error_count}')
report.append(f'警告数: {warning_count}')
report.append('\nTop 5 IP地址:')
# 获取访问最多的5个IP地址
for ip, count in ip_counter.most_common(5):
report.append(f'{ip}: {count} 次')
# 写入报告文件
try:
with open(output_file_path, 'w', encoding='utf-8') as output_file:
output_file.write('\n'.join(report))
print(f'报告已生成: {output_file_path}')
except Exception as e:
print(f'写入报告文件时出错: {e}')
# 使用示例
log_file = 'server.log'
output_file = 'log_report.txt'
analyze_log_file(log_file, output_file)
代码说明:
这个日志分析器读取日志文件,统计总行数、错误数、警告数和访问最多的IP地址
使用Counter对象高效统计IP地址出现次数
生成报告并写入输出文件
包含了完整的异常处理,确保程序的健壮性
使用with语句自动管理文件资源
11. 总结
本教程详细介绍了Python文件读写的各种方法,从基础操作到高级技巧,涵盖了文本文件和二进制文件的处理、文件和目录操作、上下文管理器、文件指针定位、高效文件处理方法以及异常处理等方面。
关键要点总结:
文件打开模式:理解不同模式(r, w, a, b, +, x)的含义和组合使用方式
资源管理:始终使用with语句自动管理文件资源,避免资源泄漏
编码处理:处理文本文件时,务必指定正确的编码方式(如utf-8)
大文件处理:对于大文件,应使用逐行读取或分块读取的方式,避免内存不足
二进制文件:处理非文本文件时使用二进制模式(b),注意字节和字符串的区别
异常处理:捕获并处理文件操作中可能出现的各种异常,增强程序健壮性
路径操作:使用os.path模块进行跨平台兼容的路径操作
文件指针:理解文件指针的工作原理,掌握seek和tell方法的使用
掌握这些技术后,你将能够高效、安全地处理各种文件操作任务,无论是简单的配置文件读写还是复杂的大数据处理。Python的文件操作功能强大而灵活,是每个Python开发者必须掌握的核心技能之一。







