你是不是也遇到过这种情况:
f = open('data.txt', 'r')
data = f.read()
print(data)
然后程序报错:
ValueError: I/O operation on closed file.
你一脸懵逼:文件明明刚打开,怎么就关闭了?
经过一番折腾,你才发现需要在finally里加f.close(),或者用with语句。但即便如此,还是各种编码问题、路径问题、性能问题接踵而至。
今天咱们就来彻底搞懂Python文件读写,让你从小白瞬间变大牛。
1. 用with语句,别问我为什么
还在用这种方式写文件操作?
# ❌ 危险操作,容易忘记关闭文件
f = open('test.txt', 'w')
f.write('Hello World')
# 如果这里抛异常,文件就永远无法关闭了!
f.close()
或者这种?
# ❌ 看似安全,实则繁琐
f = open('test.txt', 'w')
try:
f.write('Hello World')
finally:
f.close()
正确的姿势是这样的:
# ✅ 优雅、安全、自动关闭
with open('test.txt', 'w', encoding='utf-8') as f:
f.write('Hello World')
# 退出with块自动关闭文件,即使出现异常也没问题
with语句就像是给你请了个贴身保镖,无论发生什么,都会确保文件被正确关闭。这不比你手动管理强100倍?
2. 编码问题?一招制敌
# ❌ 编码噩梦的开始
with open('chinese.txt', 'r') as f:
content = f.read()
# 运行结果:UnicodeDecodeError: 'gbk' codec can't decode byte 0xff...
看到这个错误,你是不是想砸电脑?
记住这个黄金法则:
# ✅ 永远指定编码
with open('chinese.txt', 'r', encoding='utf-8') as f:
content = f.read()
为什么是utf-8?
- 它是互联网的通用语言
- 支持全世界所有字符
- 基本不会出现乱码问题
特殊情况处理:
# 读Windows记事本保存的文件
with open('notepad.txt', 'r', encoding='gbk') as f:
content = f.read()
# 处理编码不确定的文件
def read_file_safe(filename):
encodings = ['utf-8', 'gbk', 'latin1']
for encoding in encodings:
try:
with open(filename, 'r', encoding=encoding) as f:
return f.read()
except UnicodeDecodeError:
continue
raise ValueError("无法识别文件编码")
3. 一次性读取 vs 逐行读取,性能差10倍
# ❌ 内存杀手,文件大了直接爆内存
with open('big_file.txt', 'r') as f:
content = f.read() # 100MB文件 = 100MB内存占用
lines = content.split('\n')
聪明人的写法:
# ✅ 内存友好,无论文件多大都只占用一行内存
with open('big_file.txt', 'r') as f:
for line in f: # 逐行读取,内存占用几乎为0
print(line.strip())
具体场景选择:
# 小文件(<10MB),直接读取,简单粗暴
with open('small.txt', 'r') as f:
content = f.read()
# 中等文件(10MB-100MB),逐行处理
with open('medium.txt', 'r') as f:
for line in f:
process_line(line)
# 大文件(>100MB),批量处理
def process_large_file(filename, batch_size=1000):
with open(filename, 'r') as f:
batch = []
for line in f:
batch.append(line.strip())
if len(batch) >= batch_size:
process_batch(batch)
batch = []
if batch: # 处理最后一批
process_batch(batch)
4. 路径处理的优雅姿势
# ❌ Windows用户噩梦
with open('C:\\Users\\张三\\Documents\\data.txt', 'r') as f:
content = f.read()
# ❌ 拼接路径容易出错
import os
path = 'data' + '\\' + 'subdir' + '\\' + 'file.txt'
用pathlib,让路径处理变成艺术:
# ✅ 跨平台,自动处理分隔符
from pathlib import Path
# 基础用法
path = Path('data') / 'subdir' / 'file.txt'
with open(path, 'r') as f:
content = f.read()
# 高级操作
data_dir = Path.home() / 'documents' / 'data'
data_dir.mkdir(exist_ok=True) # 自动创建目录
# 读取当前目录下的所有txt文件
txt_files = Path('.').glob('*.txt')
for txt_file in txt_files:
with open(txt_file, 'r') as f:
print(f"处理文件: {txt_file.name}")
路径操作对比表:
| 操作 | os.path | pathlib |
|---|---|---|
| 拼接路径 | os.path.join('a', 'b') | Path('a') / 'b' |
| 获取文件名 | os.path.basename(path) | path.name |
| 获取扩展名 | os.path.splitext(path)[1] | path.suffix |
| 判断是否存在 | os.path.exists(path) | path.exists() |
| 创建目录 | os.makedirs(path) | path.mkdir() |
5. 文件读写的瑞士军刀:json、csv、pickle
读取JSON文件:
# ❌ 手动解析,容易出错
import json
with open('data.json', 'r') as f:
content = f.read()
data = json.loads(content) # 两步操作,何必呢?
# ✅ 一行搞定
import json
with open('data.json', 'r') as f:
data = json.load(f) # 直接从文件读取并解析
写入JSON文件:
data = {'name': '张三', 'age': 25}
# ✅ 美化输出,人类可读
with open('person.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
处理CSV文件:
# ❌ 手动处理分隔符,容易出错
with open('data.csv', 'r') as f:
for line in f:
fields = line.strip().split(',')
# 如果字段里包含逗号就炸了...
# ✅ 专业工具,自动处理转义
import csv
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f) # 自动变成字典
for row in reader:
print(row['name'], row['age'])
对象序列化(pickle):
import pickle
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 保存对象
person = Person('张三', 25)
with open('person.pkl', 'wb') as f: # 注意:二进制模式
pickle.dump(person, f)
# 读取对象
with open('person.pkl', 'rb') as f: # 注意:二进制模式
loaded_person = pickle.load(f)
print(loaded_person.name, loaded_person.age)
实战案例:日志分析工具
学了这么多,来个实战练练手:
from pathlib import Path
import json
from datetime import datetime
def analyze_logs(log_dir='logs'):
"""分析日志文件,统计错误类型"""
log_path = Path(log_dir)
error_stats = {}
# 遍历所有日志文件
for log_file in log_path.glob('*.log'):
print(f"分析文件: {log_file.name}")
with open(log_file, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
if 'ERROR' in line:
# 提取错误类型(简单示例)
error_type = line.split('ERROR')[1].strip()[:50]
error_stats[error_type] = error_stats.get(error_type, 0) + 1
# 保存分析结果
report = {
'analysis_time': datetime.now().isoformat(),
'total_errors': sum(error_stats.values()),
'error_types': error_stats
}
with open('error_report.json', 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print(f"分析完成!发现 {report['total_errors']} 个错误")
return report
# 运行分析
if __name__ == '__main__':
analyze_logs()
总结
记住这几个要点,你的Python文件读写技能就能超越80%的程序员:
- 永远用with语句 - 自动管理资源,避免忘记关闭文件
- 明确指定编码 - 用utf-8,避免中文乱码
- 大文件逐行读取 - 节省内存,提高性能
- 用pathlib处理路径 - 跨平台兼容,代码更优雅
- 善用专业模块 - json、csv、pickle让数据处理更简单
文件读写看似简单,但细节决定成败。掌握了这些技巧,你的代码会更稳定、更高效、更优雅。
下次写文件操作时,别再硬写了,用这些骚操作,让你的同事都投来崇拜的目光!
你在项目中遇到过哪些文件读写的坑?评论区分享一下,让大家都避避坑!