Python文件操作与数据持久化
目录
1. 文件操作基础
1.1 打开文件
Python中的文件操作基于文件对象模型。open()函数负责建立程序与文件系统之间的连接,返回一个文件对象供后续操作使用。
语法结构
file_object = open(filename, mode='r', encoding=None)
参数说明:
- filename: 文件路径字符串,支持相对路径和绝对路径
- mode: 文件访问模式,控制读写权限和处理方式
- encoding: 字符编码格式,处理文本文件时必须指定
1.2 文件模式类型
文件模式决定了对文件的操作权限和行为方式:
模式 | 读取 | 写入 | 创建 | 截断 | 指针位置 |
---|---|---|---|---|---|
'r' | ✓ | ✗ | ✗ | ✗ | 开头 |
'w' | ✗ | ✓ | ✓ | ✓ | 开头 |
'a' | ✗ | ✓ | ✓ | ✗ | 末尾 |
'r+' | ✓ | ✓ | ✗ | ✗ | 开头 |
'w+' | ✓ | ✓ | ✓ | ✓ | 开头 |
'a+' | ✓ | ✓ | ✓ | ✗ | 末尾 |
二进制模式通过添加'b'后缀实现,如'rb'、'wb'等。二进制模式用于处理非文本文件(图片、音频、视频等)。
# 文本模式示例
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: 基础英文编码,只支持英文字符
# 正确的编码处理方式
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__方法负责关闭文件,无论是否发生异常
这种机制确保了资源的正确释放,即使在异常情况下也能保证文件被关闭。
# 传统方式(不推荐)
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语句支持同时管理多个文件,这在文件转换和数据处理中非常有用:
# 同时操作两个文件
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()方法:
- 功能:读取所有行,返回行列表
- 特点:一次性读取整个文件,每行作为列表元素
- 返回:字符串列表
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):
优势:内存占用恒定,可处理任意大小文件
劣势:不能随机访问文件内容
适用:大文件处理、流式数据处理
# 小文件全读取
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()每次只在内存中保存一行。
# 内存效率对比示例
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'模式): 在文件末尾添加内容,保留原数据。适用于日志记录和数据收集。
# 覆盖写入 - 生成新报告
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()更高效,因为它能减少系统调用次数。
# 低效率写法
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(起始位置)
- 用途:随机访问文件内容
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:从文件末尾计算偏移
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对象支持使用/操作符进行路径连接,代码更加简洁易读。
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对象有多种创建方式,适应不同的使用场景:
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提供了完整的目录操作功能。
目录存在性检查 在进行目录操作之前,检查目录是否存在是一个良好的编程习惯:
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:如果目录已存在,不抛出异常
# 创建多级目录结构
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()方法:
- 功能:使用通配符模式搜索文件
- 支持:*(匹配任意字符)、**(递归匹配子目录)
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对象提供了丰富的属性来获取路径的各个组成部分,这些信息在文件处理中经常用到。
路径组成部分 每个路径都可以分解为多个组成部分,理解这些部分有助于灵活处理文件路径:
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()方法返回文件的详细信息,包括大小、修改时间等:
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序列化的核心是对象的状态保存。它不仅保存对象的数据,还保存对象的类型信息,甚至可以处理复杂的对象引用关系:
- 完整性:保存对象的完整状态,包括属性和方法信息
- 引用处理:正确处理对象间的引用关系,包括循环引用
- 类型信息:保存对象的类型,反序列化时能够重建正确的对象
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版本间可能存在兼容问题
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类型 | 转换说明 |
---|---|---|
dict | object | 键必须是字符串 |
list, tuple | array | tuple会转换为数组 |
str | string | 字符串保持不变 |
int, float | number | 数值类型 |
True, False | true, false | 布尔值 |
None | null | 空值 |
不支持的类型(如datetime、Decimal)需要自定义转换处理:
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不直接支持的数据类型,可以通过自定义编码器来处理:
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文件的完整功能:
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操作更加直观和安全:
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文件可能存在格式不规范的情况,需要进行适当的处理:
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等电子表格软件兼容
- 大量行式数据的批量处理
- 数据分析和报表生成