4. 联合计算中的文件交互

在MATLAB与Python联合计算的项目中,文件往往是两个运行时之间最稳定、最容易调试的“接口”。与进程内直接调用相比,文件交互的优点是解耦:MATLAB负责数值计算与工具箱处理,Python负责数据清洗、机器学习或流程编排,双方通过约定好的文件格式和目录结构交换数据。代价是必须把格式、类型、编码、路径和生命周期管理清楚,否则很容易出现“数据写进去了但读出来不对”“临时文件残留导致磁盘爆满”“并发运行时互相覆盖”等问题。本节围绕四类典型媒介展开:MAT文件、CSV/Excel、HDF5,以及临时文件管理策略。

一、MAT文件:MATLAB原生格式的双向读写

MAT文件是MATLAB的原生二进制容器,适合保存矩阵、结构体、元胞数组等复杂变量,并保留变量名与数据类型。Python端通过 scipy.io 读写:loadmat 返回字典,savemat 接收字典。需要特别注意三点:第一,loadmat 默认会加入 __header__、__version__、__globals__ 等元信息键,遍历时应过滤;第二,MATLAB的列主序与NumPy的行主序在内存布局上不同,但 scipy.io 会按逻辑形状正确转换,真正需要注意的是自己用 reshape 或按字节解析时不要搞错顺序;第三,字符串、元胞、结构体在Python侧会映射为 numpy.ndarray 嵌套对象,取值时要逐层解包。

# Python 写入 MAT,供 MATLAB 读取
import numpy as np
from scipy.io import savemat, loadmat

result = {
    "time": np.arange(0, 1.0, 0.01),          # 100 点时间轴
    "signal": np.sin(2 * np.pi * 5 * np.arange(0, 1.0, 0.01)),
    "meta": {"fs": 100, "unit": "V"}           # 结构体
}
savemat("out/result.mat", result, do_compression=True)

d = loadmat("out/result.mat")
print([k for k in d if not k.startswith("__")])  # ['time','signal','meta']

MATLAB侧对应使用 load('out/result.mat') 即可得到同名变量。工程上建议:变量名使用英文小写加下划线,避免中文与空格;对大体量数组启用 do_compression=True(MATLAB侧为 save(...,'-v7.3') 以支持大于2GB的变量);在文件头约定一个 meta 结构体,写入采样率、单位、生成时间、来源脚本版本,便于回溯。

二、CSV与Excel:面向人和轻量工具的交换格式

CSV适合纯数值表格,跨语言兼容性最好,但不携带类型信息,且存在编码与分隔符陷阱。Python用 pandas.to_csv 写出时,建议显式指定 encoding="utf-8-sig"(让Excel正确识别中文)、index=False(避免多出一列无名索引)、float_format="%.6g"(控制精度与体积)。MATLAB侧用 readtable 读取,能自动识别表头并保留列名,比 csvread 更稳健。

Excel(.xlsx)适合带多工作表、格式说明和人工批注的交付物。Python用 pandas.ExcelWriter 配合 openpyxl 引擎,可把原始数据、汇总统计、参数配置分别写入不同sheet;MATLAB侧用 readtable(..., 'Sheet', '参数') 按名读取。需要提醒的是:Excel单表上限约104万行,且读写速度远低于CSV,不适合作为高频中间结果的中转格式;日期列在两端解析规则不同,建议统一写成 yyyy-mm-dd HH:MM:SS 字符串或Unix时间戳。

三、HDF5:大规模、分层、带元数据的共享容器

当数据达到GB级、需要按路径组织多组数据集、或需要附加属性时,HDF5是首选。MATLAB的 -v7.3 MAT文件本质上就是HDF5,因此Python既可以用 h5py 直接读写通用HDF5,也可以在MATLAB保存为v7.3后用 h5py 读取(注意MATLAB对字符串和元胞的编码方式较特殊,纯数值数组最省心)。推荐做法是双方约定一套与语言无关的HDF5布局,而不是依赖某一端的默认序列化。

# Python 写 HDF5:/raw 存原始数据,/param 存参数,属性记录来源
import h5py, numpy as np, datetime
with h5py.File("out/run_001.h5", "w") as f:
    raw = f.create_dataset("raw/signal", data=np.random.randn(1000),
                           compression="gzip", compression_opts=4)
    raw.attrs["unit"] = "V"
    raw.attrs["fs"] = 1000
    f.create_dataset("param/gain", data=2.5)
    f.attrs["created"] = datetime.datetime.now().isoformat()
    f.attrs["producer"] = "python-preprocess v1.2"

MATLAB侧读取:h5read('out/run_001.h5','/raw/signal'),属性用 h5readatt。注意MATLAB读到的多维数组维度顺序与Python相反(HDF5按行主序存储,MATLAB按列主序呈现),对二维以上数组应在约定中明确“写入时转置”或“读取后转置”,并在文档中固定这一约定,避免两端各转一次导致结果错乱。

联合计算文件交互:格式选择与数据流 MATLAB 运行时 数值计算 / 工具箱 save / load (.mat) readtable / writetable h5read / h5write 共享文件区 /work/shared MAT 文件 矩阵/结构体,保留类型,v7.3 支持大文件 CSV / Excel 表格数据,人可读,需约定编码与精度 HDF5 (.h5) 分层数据集+属性,压缩,GB级共享 临时文件 tmp/ 唯一命名、用完即删、定期清理 Python 运行时 数据清洗 / 机器学习 scipy.io savemat/loadmat pandas read_csv/to_excel h5py 读写 / 属性 约定:变量名/列名/数据集路径统一;二维以上数组转置规则固定;meta 记录来源与版本

四、临时文件管理策略

联合计算常产生大量中间文件:预处理结果、模型权重、日志、失败重试的残留。若不加管理,会出现磁盘占满、并发任务互相覆盖、调试时无法判断哪份文件对应哪次运行。建议采用以下策略:

  • 按运行隔离目录:每次任务创建 runs/<run_id>/,run_id 由时间戳加随机后缀构成,如 20250612_143015_a7f3,两端脚本都从环境变量 RUN_DIR 读取,避免硬编码路径。
  • 原子写入:先写 xxx.tmp,写完再 os.replace 重命名为正式文件,防止另一端读到半截文件。
  • 生命周期约定:tmp/ 下文件在任务成功结束后删除;runs/ 保留最近N次,超出按时间清理;关键结果另存到 archive/ 并附 meta.json。
  • 并发安全:不同进程使用不同文件名或不同子目录,绝不共用同一个可写文件;需要共享只读大文件时,用完成后重命名的方式发布。
# 临时文件安全写入示例
import os, tempfile, json
run_dir = os.environ["RUN_DIR"]
os.makedirs(run_dir, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(dir=run_dir, suffix=".tmp")
with os.fdopen(fd, "w", encoding="utf-8") as f:
    json.dump({"status": "ok", "n": 1000}, f)
os.replace(tmp_path, os.path.join(run_dir, "summary.json"))  # 原子发布

最后给出格式选择的判断依据:小规模、需保留MATLAB复杂类型,选MAT;需要人工查看或与轻量工具交换,选CSV/Excel;数据量大、需分层组织与元数据、需跨语言长期共享,选HDF5。无论选哪种,都应在项目初期把目录结构、命名规则、类型约定和清理策略写进一份简短的接口说明,让两端脚本共同遵守——这比事后排查“读出来的数为什么不对”要省力得多。