Python 文件 IO 与数据序列化:从 CSV 到 Parquet 的完整指南

Python 文件 IO 与数据序列化完全指南:文本/二进制文件、CSV、JSON、YAML、Pickle、MessagePack、Parquet 的读写操作,涵盖 pathlib、with 上下文、编码处理、大数据分块读取等企业级最佳实践。

无论是日志处理、数据导出还是配置管理,文件 IO 和序列化都是 Python 开发者每天面对的任务。本文覆盖从基础文本操作到高性能二进制格式的完整技术栈。


目录

  1. pathlib:现代路径操作
  2. 文本文件读写
  3. 二进制文件与内存映射
  4. CSV 处理
  5. JSON 序列化
  6. YAML 配置
  7. Pickle:Python 原生序列化
  8. MessagePack:二进制 JSON
  9. Parquet:列式存储
  10. 大数据分块读取
  11. 编码处理
  12. 序列化格式选型速查
  13. 内存文件与临时文件

1. pathlib:现代路径操作

pathlib 是 Python 3.4+ 引入的面向对象路径库,已取代 os.path:

from pathlib import Path
import shutil

# 创建路径对象
p = Path("/Users/alice/data/report.txt")
# 或跨平台写法
p = Path.home() / "data" / "report.txt"

# 路径分解
print(p.name)         # report.txt(文件名)
print(p.stem)         # report(无扩展名)
print(p.suffix)       # .txt(扩展名)
print(p.parent)       # /Users/alice/data(父目录)
print(p.parts)        # ('/', 'Users', 'alice', 'data', 'report.txt')
print(p.exists())     # True / False

# 遍历目录
for item in Path(".").iterdir():
    if item.is_file():
        print(f"文件: {item.name}, 大小: {item.stat().st_size}")

# 递归查找(glob)
py_files = list(Path("src").rglob("*.py"))   # 递归找所有 .py
log_files = list(Path("logs").glob("*.log")) # 只找当前目录

# 文件操作
p = Path("hello.txt")
p.write_text("Hello, World!", encoding="utf-8")
content = p.read_text(encoding="utf-8")

# 创建目录
Path("output/2024/08").mkdir(parents=True, exist_ok=True)

# 复制/移动/删除
shutil.copy("src.txt", "dst.txt")
shutil.move("old.txt", "new.txt")
p.unlink()   # 删除文件
# p.rmdir()  # 删除空目录

2. 文本文件读写

2.1 基础读写

from pathlib import Path

# 写
Path("data.txt").write_text("Line 1\nLine 2\n", encoding="utf-8")

# 读
content = Path("data.txt").read_text(encoding="utf-8")

# 逐行读取
for line in Path("data.txt").read_text(encoding="utf-8").splitlines():
    print(line.strip())

# 大文件:不一次性读入内存
with open("large.txt", "r", encoding="utf-8") as f:
    for line in f:          # 逐行迭代(内存友好)
        process(line)

2.2 追加模式与多种打开模式

# 模式说明
# 'r'  读取(默认)
# 'w'  写入(覆盖)
# 'a'  追加
# 'x'  独占创建(文件存在则报错)
# 'b'  二进制模式
# '+'  读写

with open("log.txt", "a", encoding="utf-8") as f:
    f.write("新日志行\n")

# 同时读写
with open("data.txt", "r+", encoding="utf-8") as f:
    content = f.read()
    f.seek(0)
    f.write("新头部\n" + content)

3. 二进制文件与内存映射

3.1 二进制读写

# 写入二进制
data = b"\x00\x01\x02\x03"
Path("binary.dat").write_bytes(data)

# 读取二进制
raw = Path("binary.dat").read_bytes()

# 处理图片/文件
with open("image.png", "rb") as f:
    header = f.read(8)
    if header.startswith(b"\x89PNG"):
        print("这是一个 PNG 文件")

3.2 内存映射(处理超大文件)

import mmap

# 用内存映射读取大文件(不加载到内存)
with open("huge_file.bin", "rb") as f:
    with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        # mm 像 bytes 对象一样使用
        print(mm[:100])          # 前 100 字节
        print(mm.find(b"target"))  # 查找(O(n))

4. CSV 处理

4.1 读写 CSV

import csv
from pathlib import Path

# 写 CSV
rows = [
    ["name", "age", "city"],
    ["Alice", 25, "Beijing"],
    ["Bob", 30, "Shanghai"],
]

with open("users.csv", "w", newline="", encoding="utf-8-sig") as f:
    writer = csv.writer(f)
    writer.writerows(rows)

# 读 CSV
with open("users.csv", "r", encoding="utf-8-sig") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(f"{row['name']}: {row['age']} 岁")

4.2 CSV 最佳实践

# 大文件分块读取
def read_csv_in_chunks(filepath, chunksize=10000):
    import pandas as pd
    for chunk in pd.read_csv(filepath, chunksize=chunksize):
        yield chunk

# 特定编码(Excel 导出的 CSV 常用 gbk)
# pd.read_csv("file.csv", encoding="gbk")

5. JSON 序列化

5.1 基础操作

import json
from pathlib import Path

data = {
    "name": "Alice",
    "age": 25,
    "skills": ["Python", "Go", "Rust"],
    "address": {"city": "Beijing", "zip": "100000"},
}

# 序列化(Python 对象 → JSON 字符串)
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)

# 写入文件
Path("user.json").write_text(json_str, encoding="utf-8")

# 反序列化
loaded = json.loads(json_str)
print(loaded["name"])

# 直接从文件读写
with open("user.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

with open("user.json", "r", encoding="utf-8") as f:
    data = json.load(f)

5.2 自定义序列化

import json
from datetime import datetime
from decimal import Decimal

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, Decimal):
            return float(obj)
        if isinstance(obj, set):
            return list(obj)
        return super().default(obj)

data = {
    "created_at": datetime.now(),
    "price": Decimal("99.99"),
    "tags": {"python", "json"},
}

json_str = json.dumps(data, cls=CustomEncoder, ensure_ascii=False, indent=2)
print(json_str)

6. YAML 配置

# pip install pyyaml
import yaml

config = {
    "app": {"name": "MyApp", "debug": False},
    "database": {"host": "localhost", "port": 5432},
}

# 写 YAML
with open("config.yaml", "w", encoding="utf-8") as f:
    yaml.dump(config, f, allow_unicode=True, sort_keys=False)

# 读 YAML
with open("config.yaml", "r", encoding="utf-8") as f:
    loaded = yaml.safe_load(f)

print(loaded["database"]["host"])

7. Pickle:Python 原生序列化

import pickle
from pathlib import Path

# 序列化任意 Python 对象
data = {"numbers": [1, 2, 3], "func": lambda x: x**2}

pickled = pickle.dumps(data)
print(f"Pickle 大小: {len(pickled)} bytes")

# 写入文件
Path("data.pkl").write_bytes(pickled)

# 反序列化
loaded = pickle.loads(Path("data.pkl").read_bytes())
print(loaded["numbers"])

# 注意:Pickle 不安全!不要反序列化不受信任的数据

8. MessagePack:二进制 JSON

# pip install msgpack
import msgpack

data = {"name": "Alice", "age": 25, "scores": [95, 88, 92]}

# 序列化(比 JSON 更快、更小)
packed = msgpack.packb(data)
print(f"MessagePack 大小: {len(packed)} bytes")

# 反序列化
loaded = msgpack.unpackb(packed)

9. Parquet:列式存储

# pip install pyarrow
import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie"],
    "age": [25, 30, 35],
    "salary": [5000.0, 6000.0, 7000.0],
})

# 写入 Parquet(列式存储,压缩率高)
df.to_parquet("data.parquet", engine="pyarrow", compression="snappy")

# 读取(只读需要的列,非常快)
df = pd.read_parquet("data.parquet", columns=["name", "age"])

10. 大数据分块读取

from pathlib import Path

def process_large_file(filepath, process_func, chunk_size=8192):
    """分块处理大文件"""
    with open(filepath, "rb") as f:
        while chunk := f.read(chunk_size):
            process_func(chunk)

# 逐行处理超大日志
def process_logs(filepath):
    with open(filepath, "r", encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# 使用
for line in process_logs("huge.log"):
    if "ERROR" in line:
        print(line)

11. 编码处理

# 自动检测编码
# pip install chardet
import chardet

raw = Path("unknown.txt").read_bytes()
result = chardet.detect(raw)
print(f"编码: {result['encoding']}, 置信度: {result['confidence']}")

# 按检测到的编码解码
text = raw.decode(result["encoding"])

# BOM 处理
# utf-8-sig 自动处理 UTF-8 BOM
Path("with_bom.txt").write_text("hello", encoding="utf-8-sig")

12. 序列化格式选型速查

不同场景下选择合适的数据序列化格式,直接影响 I/O 性能和存储成本:

格式可读性大小速度Schema跨语言适用场景
JSON✅ 极好大中❌ 无极广API 通信、配置文件、日志
YAML✅ 好大慢❌ 无广复杂配置文件、K8s 编排
CSV✅ 好大中❌ 无极广Excel 互通、简单表格导出
Pickle❌ 二进制小快✅ Python 类型Python 专用临时缓存、模型保存
MessagePack❌ 二进制小快❌ 无广高吞吐 RPC、实时通信
Parquet❌ 二进制极小极快✅ Arrow Schema广大数据 OLAP、数据湖
Avro❌ 二进制小快✅ Schema Registry广Kafka 消息、数据管道
ProtoBuf❌ 二进制极小极快✅ .proto极广gRPC、微服务通信

选型决策树:

需要人可读?
  ├── 是 → 结构简单?
  │          ├── 是 → JSON
  │          └── 否 → YAML
  └── 否 → 大数据量/分析场景?
             ├── 是 → Parquet(列存)
             └── 否 → 跨语言 RPC?
                        ├── 是 → ProtoBuf / MessagePack
                        └── 否 → Pickle(仅限 Python 内部)

13. 内存文件与临时文件

10.1 StringIO / BytesIO(内存中操作文件)

当无需持久化到磁盘时,使用内存文件可减少 I/O 开销:

from io import StringIO, BytesIO

# 模拟文本文件操作(不落地磁盘)
output = StringIO()
output.write("第一行\n")
output.write("第二行\n")

# 获取完整内容
output.seek(0)
content = output.read()
print(content)

# BytesIO 处理二进制数据
bio = BytesIO()
bio.write(b"\x00\x01\x02\x03")
bio.seek(0)
print(bio.read())     # b'\x00\x01\x02\x03'

典型应用场景:

  • 单元测试中模拟文件对象,避免创建临时文件
  • Web 应用中动态生成 CSV/Excel 直接响应,不占用磁盘
  • 中间数据处理管道中暂存序列化结果

10.2 安全临时文件处理

import tempfile
from pathlib import Path

# 方案 1:自动清理的临时文件
with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=True) as f:
    f.write("name,age\nAlice,25\n")
    f.flush()
    # 处理完成后自动删除

# 方案 2:临时目录(多个文件)
with tempfile.TemporaryDirectory() as tmpdir:
    raw_path = Path(tmpdir) / "raw_data.json"
    raw_path.write_text('{"key": "value"}', encoding="utf-8")
    processed_path = Path(tmpdir) / "processed.parquet"
    # 处理逻辑...
    # 退出 with 后整个目录自动清理

# 方案 3:跨平台安全路径(避免 /tmp 竞态条件)
secure_tmp = Path(tempfile.mkstemp(suffix=".dat")[1])

延伸阅读


文件 IO 和序列化是数据工程的基石。选对格式(文本 vs 二进制 vs 列式)、处理好编码、掌握内存友好的读取方式,是高效处理数据的前提。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「python」更多文章

  1. 《Python高级编程》目录
  2. 《Python高级编程》11.3 PEP 流程与版本迁移策略
  3. 《Python高级编程》11.2 嵌入式与自由线程运行时