包含大型Pandas DataFrame的Python对象最优序列化方案
高效序列化含大型Pandas DataFrame的Python对象
Joblib:仍适用的高效选择
- Joblib针对NumPy数组和Pandas对象做了专门优化,比标准pickle更高效——它会跳过Python对象层级的序列化,直接操作底层数值缓冲区,既提升速度又缩小文件体积。
- 用法非常简便,直接传入你的
MyWorld实例即可,无需额外处理DataFrame字段:from joblib import dump, load import pandas as pd from dataclasses import dataclass @dataclass class MyWorld: samples: pd.DataFrame addresses: pd.DataFrame # 生成测试用大型对象 big_data = MyWorld( samples=pd.DataFrame({'col': range(1_000_000)}), addresses=pd.DataFrame({'addr': ['a']*1_000_000}) ) # 序列化(zstd压缩在速度和压缩比上平衡最优) dump(big_data, 'my_world.joblib', compress='zstd') # 反序列化 loaded_data = load('my_world.joblib') - 压缩参数优先选
zstd或lz4,比默认的gzip在速度和压缩效率上表现更好。
其他高效序列化工具
CloudPickle + 压缩
- CloudPickle是pickle的扩展,支持序列化更多Python现代结构(比如dataclass),配合压缩工具可兼顾兼容性与效率:
import cloudpickle import zstandard as zstd # 序列化 with open('my_world.zst', 'wb') as f: compressor = zstd.ZstdCompressor() with compressor.stream_writer(f) as compressed_f: cloudpickle.dump(big_data, compressed_f) # 反序列化 with open('my_world.zst', 'rb') as f: decompressor = zstd.ZstdDecompressor() with decompressor.stream_reader(f) as decompressed_f: loaded_data = cloudpickle.load(decompressed_f) - 这种方式兼容性极强,所有能运行Python的环境都能解析,压缩后的文件体积接近Parquet水平。
Dill
- Dill同样是pickle的扩展,支持更多Python对象结构的序列化,配合压缩使用时,处理大型DataFrame的速度和文件体积都优于原生pickle,对dataclass的支持也很完善。
Pandas+Parquet半自动方案(比手动处理简便)
如果不想完全依赖pickle系工具,又不想逐个手动保存DataFrame,可以写一套通用函数,自动把dataclass里的DataFrame存为Parquet,其他字段用pickle:
import pandas as pd import pickle from dataclasses import fields def save_my_world(obj: MyWorld, path: str): # 保存非DataFrame类型的字段 meta = {f.name: getattr(obj, f.name) for f in fields(obj) if not isinstance(getattr(obj, f.name), pd.DataFrame)} with open(f"{path}_meta.pkl", 'wb') as f: pickle.dump(meta, f) # 自动将DataFrame保存为Parquet for f in fields(obj): val = getattr(obj, f.name) if isinstance(val, pd.DataFrame): val.to_parquet(f"{path}_{f.name}.parquet", compression='zstd') def load_my_world(path: str) -> MyWorld: # 加载元数据 with open(f"{path}_meta.pkl", 'rb') as f: meta = pickle.load(f) # 加载Parquet格式的DataFrame for f in fields(MyWorld): if f.name not in meta: meta[f.name] = pd.read_parquet(f"{path}_{f.name}.parquet") return MyWorld(**meta)
这套方案只需要写一次通用函数,后续无需手动处理每个DataFrame,Parquet的压缩率和读写速度都很出色,文件体积也是最小的。
方案对比
| 方案 | 速度 | 文件大小 | 简便性 | 兼容性 |
|---|---|---|---|---|
| Joblib(带压缩) | 快 | 较小 | 极高 | 好 |
| CloudPickle+压缩 | 中快 | 较小 | 高 | 极好 |
| Dill+压缩 | 中快 | 较小 | 高 | 好 |
| Pandas+Parquet半自动 | 快(读写) | 最小 | 较高 | 依赖Pandas |
内容的提问来源于stack exchange,提问作者Mikko Ohtamaa
相关产品推荐
相关产品推荐

