Skip to content

Python文件操作与数据持久化

目录

  1. 1. 文件操作基础
  1. 2. 文件读取
  1. 3. 文件写入
  1. 4. 文件指针操作
  1. 5. 目录操作(pathlib)
  1. 6. 数据持久化

1. 文件操作基础

1.1 打开文件

Python中的文件操作基于文件对象模型。open()函数负责建立程序与文件系统之间的连接,返回一个文件对象供后续操作使用。

语法结构

python
file_object = open(filename, mode='r', encoding=None)

参数说明:

  • filename: 文件路径字符串,支持相对路径和绝对路径
  • mode: 文件访问模式,控制读写权限和处理方式
  • encoding: 字符编码格式,处理文本文件时必须指定

1.2 文件模式类型

文件模式决定了对文件的操作权限和行为方式:

模式读取写入创建截断指针位置
'r'开头
'w'开头
'a'末尾
'r+'开头
'w+'开头
'a+'末尾

二进制模式通过添加'b'后缀实现,如'rb'、'wb'等。二进制模式用于处理非文本文件(图片、音频、视频等)。

python
# 文本模式示例
with open("data.txt", "r", encoding="utf-8") as f:
    text_content = f.read()

# 二进制模式示例
with open("image.jpg", "rb") as f:
    binary_data = f.read()

1.3 编码处理

文本文件的编码参数至关重要。不同的编码格式决定了字符在文件中的存储方式:

  • UTF-8: 现代标准,支持所有Unicode字符,推荐使用
  • GBK/GB2312: 中文编码,用于处理老式中文文档
  • ASCII: 基础英文编码,只支持英文字符
python
# 正确的编码处理方式
with open("chinese_text.txt", "r", encoding="utf-8") as f:
    content = f.read()

# 处理编码错误的情况
with open("unknown_encoding.txt", "r", encoding="utf-8", errors="ignore") as f:
    content = f.read()  # 忽略无法解码的字符

1.4 with语句(推荐方式)

with语句实现了Python的上下文管理协议,专门用于资源管理。它解决了传统文件操作中的资源泄露问题。

工作原理 当代码块内的内容执行完成后就自动关闭文件! with语句在进入代码块时调用对象的__enter__方法,在退出时调用__exit__方法。对于文件对象:

  • __enter__方法返回文件对象本身
  • __exit__方法负责关闭文件,无论是否发生异常

这种机制确保了资源的正确释放,即使在异常情况下也能保证文件被关闭。

python
# 传统方式(不推荐)
f = open("example.txt", "r", encoding="utf-8")
try:
    content = f.read()
    # 如果这里发生异常,文件可能不会被关闭
finally:
    f.close()

# with语句方式(推荐)
with open("example.txt", "r", encoding="utf-8") as f:
    content = f.read()
    # 文件会自动关闭,即使发生异常

1.5 多文件操作

with语句支持同时管理多个文件,这在文件转换和数据处理中非常有用:

python
# 同时操作两个文件
with open("input.txt", "r", encoding="utf-8") as infile, \
     open("output.txt", "w", encoding="utf-8") as outfile:
    
    data = infile.read()
    processed_data = data.upper()  # 转换为大写
    outfile.write(processed_data)
# 两个文件都会被自动关闭

2. 文件读取

2.1 读取方法对比

文件对象提供了多种读取方法,理解它们的区别有助于选择最适合的操作方式。

read(size)方法:

  • 功能:读取指定字节数的内容,不指定则读取全部
  • 特点:一次性加载到内存,适合小文件
  • 返回:字符串(文本模式)或字节对象(二进制模式)

readline()方法:

  • 功能:读取一行内容,包括换行符
  • 特点:每次只读一行,内存占用小
  • 返回:单行字符串,文件末尾返回空字符串

readlines()方法:

  • 功能:读取所有行,返回行列表
  • 特点:一次性读取整个文件,每行作为列表元素
  • 返回:字符串列表
python
with open("example.txt", "r", encoding="utf-8") as f:
    # 读取前10个字符
    partial_content = f.read(10)
    
    # 回到文件开头
    f.seek(0)
    
    # 读取第一行
    first_line = f.readline()
    
    # 回到文件开头,读取所有行
    f.seek(0)
    all_lines = f.readlines()

2.2 读取策略与内存管理

文件读取策略的选择直接影响程序的内存使用和处理效率。

全文读取 vs 逐行读取

  • 全文读取 (read(), readlines()):

  • 优势:处理简单,数据一次性可用

  • 劣势:大文件会占用大量内存

  • 适用:小文件(MB级别以下)、需要全文分析的场景

  • 逐行读取 (readline(), for line in f):

  • 优势:内存占用恒定,可处理任意大小文件

  • 劣势:不能随机访问文件内容

  • 适用:大文件处理、流式数据处理

python
# 小文件全读取
with open("config.json", "r", encoding="utf-8") as f:
    config_data = f.read()  # 一次性读取全部内容
    # 对配置数据进行解析处理

# 大文件逐行处理
line_count = 0
with open("big_log.txt", "r", encoding="utf-8") as f:
    for line in f:  # 逐行迭代,内存友好
        if "ERROR" in line:
            print(f"第{line_count}行发现错误: {line.strip()}")
        line_count += 1

2.3 内存效率对比

理解Python文件读取的内存机制有助于优化程序性能:

  • read()和readlines()方法会将整个文件内容加载到内存中。
  • for line in file迭代器和readline()每次只在内存中保存一行。
python
# 内存效率对比示例
def count_lines_method1(filename):
    """方法1:全文读取后分割(内存占用高)"""
    with open(filename, "r", encoding="utf-8") as f:
        content = f.read()
        return len(content.splitlines())

def count_lines_method2(filename):
    """方法2:逐行计数(内存占用低)"""
    count = 0
    with open(filename, "r", encoding="utf-8") as f:
        for line in f:
            count += 1
    return count

3. 文件写入

3.1 写入方法与模式

文件写入操作将程序中的数据持久化存储到磁盘。

写入方法

  • write(string): 写入字符串到文件,返回写入的字符数。不会自动添加换行符。
  • writelines(lines): 写入一个字符串序列(如列表),批量写入可提高IO效率。同样不会自动添加换行符。

写入模式

  • 覆盖写入 ('w'模式): 清空原文件内容,从头开始写入。适用于生成新文件或重写。
  • 追加写入 ('a'模式): 在文件末尾添加内容,保留原数据。适用于日志记录和数据收集。
python
# 覆盖写入 - 生成新报告
report_data = "系统运行正常\n内存使用率: 45%\n"
with open("system_report.txt", "w", encoding="utf-8") as f:
    f.write(report_data)

# 追加写入 - 记录日志
import datetime
log_entry = f"[{datetime.datetime.now()}] 用户登录\n"
with open("access.log", "a", encoding="utf-8") as f:
    f.write(log_entry)

3.2 批量写入优化

对于大量数据的写入,使用writelines()方法比在循环中多次调用write()更高效,因为它能减少系统调用次数。

python
# 低效率写法
with open("data.txt", "w", encoding="utf-8") as f:
    for i in range(1000):
        f.write(f"行号: {i}\n")  # 1000次系统调用

# 高效率写法
lines = [f"行号: {i}\n" for i in range(1000)]
with open("data.txt", "w", encoding="utf-8") as f:
    f.writelines(lines)  # 1次系统调用

4. 文件指针操作

4.1 指针位置管理

文件指针是文件操作中的重要概念,它标记了当前读写位置。掌握指针操作可以实现精确的文件控制。

tell()方法:

  • 功能:返回当前指针位置(字节偏移量)
  • 用途:记录位置、计算读取量
  • 返回:整数,表示从文件开头的字节数

seek()方法:

  • 功能:移动指针到指定位置
  • 参数:offset(偏移量)和whence(起始位置)
  • 用途:随机访问文件内容
python
with open("example.txt", "r", encoding="utf-8") as f:
    print(f"初始位置: {f.tell()}")  # 输出: 0
    
    # 读取5个字符
    data = f.read(5)
    print(f"读取5个字符后位置: {f.tell()}")  # 输出: 5
    
    # 移动到文件开头
    f.seek(0)
    print(f"重置后位置: {f.tell()}")  # 输出: 0

4.2 seek()方法的三种模式

seek(offset, whence)中的whence参数决定了偏移的起始位置:

  • whence=0(默认):从文件开头计算偏移
  • whence=1:从当前位置计算偏移
  • whence=2:从文件末尾计算偏移
python
with open("data.txt", "r", encoding="utf-8") as f:
    # 移动到文件中间位置
    f.seek(0, 2)  # 先到文件末尾
    file_size = f.tell()  # 获取文件大小
    f.seek(file_size // 2, 0)  # 移动到文件中间
    
    # 从中间位置读取内容
    middle_content = f.read(10)

5. 目录操作(pathlib)

5.1 pathlib基础概念

传统的os.path模块使用字符串处理路径,而pathlib引入了面向对象的路径处理方式。这种设计使得路径操作更加直观和安全。

核心优势分析

  • 跨平台兼容性:pathlib自动处理不同操作系统的路径分隔符差异(Windows使用反斜杠,Unix系统使用正斜杠),程序无需考虑平台差异。
  • 类型安全:Path对象提供了明确的方法和属性,避免了字符串操作可能出现的错误。
  • 链式操作:Path对象支持使用/操作符进行路径连接,代码更加简洁易读。
python
from pathlib import Path
import os

# 传统方式(os.path)
config_path_str = os.path.join("project", "config", "settings.json")
if os.path.exists(config_path_str):
    file_size = os.path.getsize(config_path_str)

# pathlib方式
config_path = Path("project") / "config" / "settings.json"
if config_path.exists():
    file_size = config_path.stat().st_size

5.2 Path对象的创建方式

Path对象有多种创建方式,适应不同的使用场景:

python
from pathlib import Path

# 基本路径创建
file_path = Path("data.txt")  # 相对路径
abs_path = Path("/home/user/documents")  # 绝对路径

# 特殊路径获取
current_dir = Path.cwd()  # 当前工作目录
home_dir = Path.home()    # 用户主目录

# 路径组合
project_path = current_dir / "my_project"
data_file = project_path / "data" / "input.csv"

5.3 基本目录操作

目录操作是文件管理的基础,pathlib提供了完整的目录操作功能。

目录存在性检查 在进行目录操作之前,检查目录是否存在是一个良好的编程习惯:

python
from pathlib import Path

project_dir = Path("my_project")

# 检查路径是否存在
if project_dir.exists():
    print("项目目录存在")
    
    # 进一步检查路径类型
    if project_dir.is_dir():
        print("确实是目录")
    elif project_dir.is_file():
        print("这是文件,不是目录")
else:
    print("项目目录不存在,需要创建")

目录创建策略 mkdir()方法的参数解析:

  • parents=True:创建所有必要的父目录
  • exist_ok=True:如果目录已存在,不抛出异常
python
# 创建多级目录结构
project_structure = Path("my_app/data/processed")
project_structure.mkdir(parents=True, exist_ok=True)

# 这等价于创建:
# my_app/
# ├── data/
#     └── processed/

# 只创建单级目录(父目录必须存在)
log_dir = Path("my_app/logs")
if not log_dir.exists():
    log_dir.mkdir()  # 如果my_app不存在,会抛出异常

5.4 目录内容遍历

pathlib提供了多种遍历目录的方法:

iterdir()方法:

  • 功能:返回目录中直接包含的项目(不递归)
  • 返回:生成器对象,产生Path对象

glob()方法:

  • 功能:使用通配符模式搜索文件
  • 支持:*(匹配任意字符)、**(递归匹配子目录)
python
project_dir = Path("my_project")

# 列举目录内容
print("项目目录内容:")
for item in project_dir.iterdir():
    item_type = "目录" if item.is_dir() else "文件"
    print(f"  {item_type}: {item.name}")

# 查找特定类型文件
python_files = list(project_dir.glob("*.py"))
print(f"找到 {len(python_files)} 个Python文件")

# 递归查找所有Python文件
all_python_files = list(project_dir.glob("**/*.py"))
print(f"项目中共有 {len(all_python_files)} 个Python文件")

5.5 路径信息获取

Path对象提供了丰富的属性来获取路径的各个组成部分,这些信息在文件处理中经常用到。

路径组成部分 每个路径都可以分解为多个组成部分,理解这些部分有助于灵活处理文件路径:

python
from pathlib import Path

file_path = Path("/home/user/documents/report.docx")

# 基本信息
print(f"完整路径: {file_path}")
print(f"文件名: {file_path.name}")           # report.docx
print(f"文件主名: {file_path.stem}")          # report
print(f"文件扩展名: {file_path.suffix}")      # .docx
print(f"所有扩展名: {file_path.suffixes}")    # ['.docx']

# 目录信息
print(f"父目录: {file_path.parent}")          # /home/user/documents
print(f"所有父目录: {list(file_path.parents)}")  # [/home/user/documents, /home/user, /home, /]

5.6 文件属性获取

Path对象的stat()方法返回文件的详细信息,包括大小、修改时间等:

python
import datetime
from pathlib import Path

file_path = Path("report.docx") # 假设文件存在

if file_path.exists():
    stat_info = file_path.stat()
    
    # 文件大小(字节)
    file_size = stat_info.st_size
    print(f"文件大小: {file_size} 字节")
    
    # 修改时间
    mod_time = datetime.datetime.fromtimestamp(stat_info.st_mtime)
    print(f"最后修改时间: {mod_time}")
    
    # 转换为绝对路径
    absolute_path = file_path.resolve()
    print(f"绝对路径: {absolute_path}")

6. 数据持久化

6.1 pickle序列化

pickle是Python的内置序列化库,它能够将Python对象转换为字节流进行存储,并能够完整地恢复对象的状态。

序列化原理 pickle序列化的核心是对象的状态保存。它不仅保存对象的数据,还保存对象的类型信息,甚至可以处理复杂的对象引用关系:

  • 完整性:保存对象的完整状态,包括属性和方法信息
  • 引用处理:正确处理对象间的引用关系,包括循环引用
  • 类型信息:保存对象的类型,反序列化时能够重建正确的对象
python
import pickle

# 复杂数据结构示例
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.courses = []
    
    def add_course(self, course):
        self.courses.append(course)

# 创建对象
student = Student("张三", 20)
student.add_course("数学")
student.add_course("物理")

# 序列化对象到文件
with open('student.pkl', 'wb') as f:
    pickle.dump(student, f)

# 从文件反序列化对象
with open('student.pkl', 'rb') as f:
    loaded_student = pickle.load(f)
    print(f"学生姓名: {loaded_student.name}")
    print(f"选修课程: {loaded_student.courses}")

pickle的应用场景和限制 适用场景:

  • 缓存计算结果,避免重复计算
  • 保存程序运行状态
  • Python程序间的数据传递

重要限制:

  • 只能在Python环境中使用
  • 安全风险:不能反序列化不可信的数据
  • 版本兼容性:不同Python版本间可能存在兼容问题
python
import pickle
from pathlib import Path

# 缓存计算结果的示例
def expensive_computation(data):
    """模拟耗时计算"""
    result = sum(x**2 for x in data)  # 简化的计算
    return result

def cached_computation(data, cache_file="cache.pkl"):
    """带缓存的计算函数"""
    cache_path = Path(cache_file)
    
    if cache_path.exists():
        # 从缓存加载结果
        with open(cache_path, 'rb') as f:
            cached_result = pickle.load(f)
            print("使用缓存结果")
            return cached_result
    
    # 执行计算并缓存
    result = expensive_computation(data)
    with open(cache_path, 'wb') as f:
        pickle.dump(result, f)
    print("计算完成并缓存")
    return result

6.2 JSON格式处理

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有良好的可读性和广泛的语言支持。

JSON的数据类型映射 JSON格式只支持有限的数据类型,Python的json模块在序列化时会自动进行类型转换:

Python类型JSON类型转换说明
dictobject键必须是字符串
list, tuplearraytuple会转换为数组
strstring字符串保持不变
int, floatnumber数值类型
True, Falsetrue, false布尔值
Nonenull空值

不支持的类型(如datetime、Decimal)需要自定义转换处理:

python
import json
from datetime import datetime

# 基本JSON操作
config_data = {
    "app_name": "我的应用",
    "version": "1.0.0",
    "debug": True,
    "max_connections": 100,
    "allowed_hosts": ["localhost", "127.0.0.1"]
}

# 保存为格式化JSON
with open('config.json', 'w', encoding='utf-8') as f:
    json.dump(config_data, f, 
              ensure_ascii=False,  # 支持中文字符
              indent=2)            # 格式化输出

# 读取JSON配置
with open('config.json', 'r', encoding='utf-8') as f:
    loaded_config = json.load(f)
    print(f"应用名称: {loaded_config['app_name']}")

处理复杂数据类型 对于JSON不直接支持的数据类型,可以通过自定义编码器来处理:

python
import json
from datetime import datetime

class DateTimeEncoder(json.JSONEncoder):
    """自定义JSON编码器,处理datetime对象"""
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

# 包含datetime的数据
log_data = {
    "event": "用户登录",
    "timestamp": datetime.now(),
    "user_id": 12345
}

# 使用自定义编码器
json_string = json.dumps(log_data, cls=DateTimeEncoder, ensure_ascii=False)
print(f"序列化结果: {json_string}")

# 反序列化时需要手动处理datetime
def parse_datetime(data):
    """解析包含datetime的JSON数据"""
    if 'timestamp' in data:
        data['timestamp'] = datetime.fromisoformat(data['timestamp'])
    return data

loaded_data = json.loads(json_string)
loaded_data = parse_datetime(loaded_data)

6.3 CSV文件操作

CSV(Comma-Separated Values)格式是处理表格数据的通用标准,特别适合在不同系统间交换结构化数据。

CSV格式特点 CSV文件的结构简单但有一些需要注意的特点:

  • 第一行通常作为列标题(header)
  • 字段间用逗号分隔,但可以自定义分隔符
  • 包含逗号或换行符的字段需要用引号包围
  • 编码格式需要统一,建议使用UTF-8

基本CSV操作 Python的csv模块提供了读写CSV文件的完整功能:

python
import csv

# CSV写入操作
students_data = [
    ["学号", "姓名", "数学", "英语", "总分"],
    ["001", "张三", 95, 87, 182],
    ["002", "李四", 78, 92, 170],
    ["003", "王五", 88, 85, 173]
]

with open('students.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    writer.writerows(students_data)  # 一次写入所有行

# CSV读取操作
with open('students.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    header = next(reader)  # 读取标题行
    print(f"列标题: {header}")
    
    for row in reader:
        print(f"学生信息: {row}")

字典形式的CSV操作 使用DictReader和DictWriter可以让CSV操作更加直观和安全:

python
import csv

# 字典形式写入CSV
fieldnames = ['学号', '姓名', '数学', '英语', '总分']
students = [
    {'学号': '001', '姓名': '张三', '数学': 95, '英语': 87, '总分': 182},
    {'学号': '002', '姓名': '李四', '数学': 78, '英语': 92, '总分': 170}
]

with open('students_dict.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()  # 写入标题行
    writer.writerows(students)

# 字典形式读取CSV
with open('students_dict.csv', 'r', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        # 每行都是一个字典,可以通过列名访问
        print(f"{row['姓名']}的数学成绩是{row['数学']}分")
        
        # 计算平均分
        math_score = int(row['数学'])
        english_score = int(row['英语'])
        average = (math_score + english_score) / 2
        print(f"平均分: {average:.1f}")

CSV文件的高级处理 在实际应用中,CSV文件可能存在格式不规范的情况,需要进行适当的处理:

python
import csv

# 处理包含特殊字符的CSV
problematic_data = [
    ['产品名称', '描述', '价格'],
    ['苹果iPhone', '智能手机,支持5G', 6999],
    ['华为MateBook', '轻薄笔记本\n高性能', 5999]
]

with open('products.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f, quoting=csv.QUOTE_ALL)  # 所有字段都加引号
    writer.writerows(problematic_data)

# 读取时处理数据类型
with open('products.csv', 'r', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        product_name = row['产品名称']
        # 价格字段转换为数值类型
        price = float(row['价格']) if row['价格'].isdigit() else 0
        print(f"{product_name}: ¥{price:.2f}")

6.4 pickle、JSON、CSV的选择

选择合适的数据存储格式对项目成功很重要:

使用pickle的场景:

  • 需要保存完整的Python对象状态
  • 数据只在Python程序间传递
  • 需要处理复杂的对象引用关系
  • 对性能要求较高的临时数据存储

使用JSON的场景:

  • 需要人类可读的配置文件
  • Web API数据交换
  • 跨编程语言的数据传递
  • 存储简单的结构化数据

使用CSV的场景:

  • 表格数据的存储和交换
  • 与Excel等电子表格软件兼容
  • 大量行式数据的批量处理
  • 数据分析和报表生成

邬东升的博客