📌 一句话提纲
Python 文件操作从 open() 到 pathlib,覆盖读写模式、编码处理、上下文管理、缓冲区控制、临时文件等实战场景。
1. 核心函数 open()
基本签名
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
常用模式一览
| 模式 | 含义 | 指针位置 | 文件不存在 |
|---|
r | 只读文本 | 开头 | 报错 |
w | 写入文本(清空) | 开头 | 创建 |
a | 追加文本 | 末尾 | 创建 |
x | 排他创建 | 开头 | 创建,存在则报错 |
r+ | 读写 | 开头 | 报错 |
w+ | 读写(清空) | 开头 | 创建 |
a+ | 读写(追加) | 末尾 | 创建 |
rb | 二进制读 | 开头 | 报错 |
wb | 二进制写 | 开头 | 创建 |
⚠️ 常见坑:w+ 不是追加
with open('test.txt', 'w+') as f:
f.write('hello')
f.read()
f.seek(0)
f.read()
2. 上下文管理器 with
✅ 正确做法(自动关闭)
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
❌ 错误做法(忘记关闭)
f = open('data.txt', 'r')
content = f.read()
同时打开多个文件
with open('a.txt') as f1, open('b.txt') as f2:
for line1, line2 in zip(f1, f2):
print(line1.strip(), line2.strip())
3. 读写方法详解
读取
with open('data.txt', 'r', encoding='utf-8') as f:
f.read()
f.read(1024)
f.readline()
f.readlines()
with open('large.log', 'r', encoding='utf-8') as f:
for line in f:
process(line)
写入
with open('out.txt', 'w', encoding='utf-8') as f:
f.write('hello\n')
f.writelines(['a\n', 'b\n'])
with open('out.txt', 'w', encoding='utf-8') as f:
print('hello', file=f)
print('a', 'b', 'c', sep=',', file=f)
4. 编码与乱码
编码解码
s = '你好世界'
b = s.encode('utf-8')
s2 = b.decode('utf-8')
s = '你好'
b = s.encode('ascii', errors='replace')
b = s.encode('ascii', errors='ignore')
b = s.encode('ascii', errors='backslashreplace')
文件编码检测
import chardet
with open('unknown.txt', 'rb') as f:
raw = f.read(10000)
result = chardet.detect(raw)
encoding = result['encoding']
confidence = result['confidence']
with open('unknown.txt', 'r', encoding=encoding) as f:
content = f.read()
⚠️ 常见乱码场景
with open('gbk.txt', 'r', encoding='utf-8') as f:
pass
with open('gbk.txt', 'r', encoding='gbk') as f:
pass
import chardet
with open('gbk.txt', 'rb') as f:
enc = chardet.detect(f.read(10000))['encoding']
with open('gbk.txt', 'r', encoding=enc) as f:
content = f.read()
5. 缓冲区控制
为什么需要 buffering?
with open('log.txt', 'w', buffering=1) as f:
f.write('即时写入\n')
with open('/dev/cu.usbserial', 'wb', buffering=0) as f:
f.write(b'\x01\x02')
手动刷新
with open('progress.txt', 'w') as f:
for i in range(10):
f.write(f'step {i}\n')
f.flush()
6. 文件指针移动 seek
with open('data.txt', 'rb') as f:
f.seek(0, 0)
f.seek(0, 2)
f.seek(10, 0)
f.seek(-5, 1)
f.seek(-10, 2)
print(f.tell())
⚠️ 文本模式限制
with open('data.txt', 'r', encoding='utf-8') as f:
f.seek(10, 0)
f.seek(5, 1)
7. 临时文件 tempfile
import tempfile
with tempfile.TemporaryFile(mode='w+t') as f:
f.write('临时数据')
f.seek(0)
print(f.read())
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as f:
f.write(b'name,age\nAlice,30\n')
temp_path = f.name
print(f'临时文件路径: {temp_path}')
with tempfile.TemporaryDirectory() as tmpdir:
file_path = Path(tmpdir) / 'test.txt'
file_path.write_text('hello')
8. pathlib — 当代 Python 文件操作标准
from pathlib import Path
p = Path('data.txt')
p.write_text('hello world', encoding='utf-8')
p.read_text(encoding='utf-8')
p.write_bytes(b'\x00\x01\x02')
p.read_bytes()
p.parent
p.stem
p.suffix
p.name
p.exists()
p.is_file()
p.is_dir()
p.stat()
dir_p = Path('/tmp')
for f in dir_p.iterdir():
print(f.name)
for f in dir_p.glob('*.txt'):
print(f)
for f in dir_p.rglob('**/*.py'):
print(f)
Path('a/b/c').mkdir(parents=True, exist_ok=True)
🧪 完整实战:日志分割器
"""将大日志按日期分割成小文件"""
from pathlib import Path
def split_log_by_date(log_path: str, output_dir: str):
log_file = Path(log_path)
out_dir = Path(output_dir)
out_dir.mkdir(parents=True, exist_ok=True)
current_date = None
current_file = None
with open(log_file, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('['):
date_part = line[1:11]
if date_part != current_date:
if current_file:
current_file.close()
current_date = date_part
current_file = open(
out_dir / f'{date_part}.log',
'a', encoding='utf-8'
)
if current_file:
current_file.write(line)
if current_file:
current_file.close()
if __name__ == '__main__':
split_log_by_date('app.log', 'split_logs')
📝 要点总结
| 要点 | 说明 |
|---|
✅ 始终用 with | 自动关闭,防泄漏 |
✅ 明确 encoding | 文本模式不指定会用平台默认编码(不同系统可能不同) |
| ✅ 大文件逐行迭代 | for line in f: 而不是 .readlines() |
✅ 用 pathlib | 比 os.path 更现代、更简洁 |
⚠️ w 会清空文件 | 不清空想追加用 a |
⚠️ w+ 指针在开头 | 读之前可能需要 seek(0) |
| ⚠️ 文本模式限制 seek | 只能用 seek(0) 和 seek(0, 2) |
💡 buffering=0 无缓冲 | 适合实时日志、串口通信 |
💡 chardet 探测编码 | 处理未知编码文件时先检测 |