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还提供了osshutil模块用于处理文件和目录操作。

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_WRITEACCESS_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开发者必须掌握的核心技能之一。

发表回复

后才能评论